2

在 C++ 中,如果我们在某个基类(比如 Base)中有一些虚函数,我们想要覆盖这个虚函数,我们将不得不再次声明这个虚函数以使其在我们的派生类中也能工作。

class Base {
public:
    virtual void virtualFunction();
    static int s_whatSoEver[];
private:
    void _privateFunction();
}
class Derived {
public:
    virtual void virtualFunction();
}

这不是很愚蠢吗,因为如果我们想更改虚函数原型,就必须更改派生的每个声明?

还有,为什么要在头文件中声明一些受保护或私有的函数,因为头文件是用来定义公共接口的,而使用这个接口的用户根本不需要关心它们呢?也许我们可以直接在 .cpp 文件中实现和声明私有或受保护的函数,就像 Objective-C 一样。

C++ 也没有静态初始化器,如果我们想初始化一些静态类变量,我们必须为此创建一个类:

class BaseStaticVariableInitializer {
public:
    BaseStaticVariableInitializer() {
        Base::s_whatSoEver = new int[20];
        for (int i = 0; i < 20; i++) {
            s_whatSoEver[i] = xxx;
        }
    }
    ~BaseStaticVariableInitializer() {
         delete [] Base::s_whatSoEver;
    }
}

并专门为其初始化一个静态类常量:

static BaseStaticVariableInitializer s_baseStaticVariableInitializer;

对不起,我的无知,但是您编写 C++ 代码以适应 DRY 的正确方法是什么?

4

4 回答 4

6

这不是很愚蠢吗,因为如果我们想更改虚函数原型,就必须更改派生的每个声明?

不,如果您要更改基类中的虚函数原型,那么您就是在更改公共接口。你不应该那样做。

还有,为什么要在头文件中声明一些受保护或私有的函数,因为头文件是用来定义公共接口的,而使用这个接口的用户根本不需要关心它们呢?

protected成员应被视为类的公共接口的一部分。protected简单地帮助您避免错误,否则您会因拥有它们而冒险public。但请不要误会:protected成员是类接口的一部分,应该这样对待。

关于private头文件中的 s:是的,我同意,在许多方面将它们仅保留在实现文件中会更合乎逻辑。但是,请考虑何时按值传递类:

foo(Bacon b)
{
    b.cook();
}

为了调用foo(),您需要让Bacon编译器可以使用完整的类定义。(注意:只有类定义,而不是其成员函数的定义。)这样编译器就可以知道调用时为类分配多少堆栈空间foo()。如果编译器还必须搜索实现文件以查找private变量,则解析会更加复杂(编译可能会更慢)。

更新

既然你提到了 DRY,我必须指出这一点。DRY 原则指出:

在系统中,每条知识都必须有一个单一的、明确的、权威的表示。

在相关类中声明虚函数并不违反这个原则。它们的功能不一样。在Base,你说那Base::foovirtual。中Derived,你说的Derived::foo也是virtualBase::fooandDerived::foo是两个独立的函数,但碰巧两者都可以通过指针或对 a 的引用来调用Base

于 2013-07-23T02:10:55.080 回答
5

这不是很愚蠢吗,因为如果我们想更改虚函数原型,就必须更改派生的每个声明?

这正是重点。如果基础中的签名发生变化,您希望编译器告诉您,而不是潜在地尝试使用错误类型调用函数。C++ 是一种静态类型的编译语言。类型是在编译时定义的,如果虚函数的类型发生变化,需要重新编译以适应变化。

为什么需要在头文件中声明一些受保护或私有的函数,因为头文件用于公共接口定义,而使用该接口的用户根本不需要关心它们?

这又是一个完全相同的设计选择。在 C++ 中,单一定义规则要求每种类型在所有翻译单元(不同的编译文件)中定义完全相同。如前所述,C++ 是一种编译语言,通常成员会影响类,而不管访问说明符(在编译过程中被删除)。当编译器创建您类型的对象时,它必须为每个和所有数据成员分配足够的空间,无论它们是公共的、私有的还是受保护的。当它构建一个虚拟表时,它需要知道需要为所有功能分配多少个插槽。可以说,非虚拟函数不会影响生成的对象/RTTI,但它们可能会。

如果在基类中添加了新的虚函数,其签名与派生类中的受保护/私有成员函数完全相同,则后者将成为前者的覆盖,并且需要在虚表中创建一个新槽。虽然这可能不太可能,但如果这些功能隐藏在单个翻译单元中(您可能有权或无权访问),您可能会遇到这些问题。

C++ 也没有静态初始化器,如果我们想初始化一些静态类变量,我们必须为此创建一个类

C++ 没有静态初始化程序,但我肯定不会为它创建一个类。静态成员变量需要在单个翻译单元中定义,并且可以在该翻译单元中对其进行初始化。在简单的情况下,您可以直接进行常规初始化,对于更复杂的情况,您可以创建一个提供初始化值的函数。

int *Base::member = new int[10](); // value initialized (set to 0)

// abusing lambdas not to write a function:
int *Base::member2 = []()->int* { 
                         int *p = new int[10];
                         for (int i = 0; i < 10; ++i) p[i] = xxx;
                         return p; }();

请注意,这不控制资源的释放(您在代码中执行此操作),但可以使用语言结构轻松处理:

std::unique_ptr<int[]> Base::member(new int[10]());
于 2013-07-23T03:03:21.007 回答
1

为什么必须在头文件中声明一些受保护或私有的函数?...

您编写 C++ 代码以适应 DRY 的正确方法是什么?

您可以使用Pimpl idiom 从头文件中隐藏实现细节(私有成员和方法签名的声明)。此外,您将获得更快的构建。Qt 广泛使用这个习语。

于 2013-07-23T10:55:37.000 回答
1

快速简短的回答。1] 对于您的第一个问题,请参阅 c++ 中覆盖和重载之间的差异 2] 初始化静态 memebr ,这可以在静态成员函数中完成

class Base {
public:
  virtual void virtualFunction();
  static int s_whatSoEver[];

  static void BaseStaticVariableInitializer() {
    Base::s_whatSoEver = new int[20];
    for (int i = 0; i < 20; i++) {
        s_whatSoEver[i] = xxx;
    }
}
private:
 void _privateFunction();

在 .cpp 文件中单独定义

 static int Base::s_whatSoEver[20];
于 2013-07-23T02:13:09.310 回答