如何让C++与DRY很好地配合?

Dav*_*vid 2 c++ dry

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

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

这不是愚蠢的,因为如果我们想改变虚函数原型,我们必须改变derived-s的每个声明吗?

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

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

并特别为它初始化一个静态类常量:

static BaseStaticVariableInitializer s_baseStaticVariableInitializer;
Run Code Online (Sandbox Code Playgroud)

对不起我的无知,但是你正确的方法来编写你的c ++代码以适应DRY?

Lst*_*tor 6

这不是愚蠢的,因为如果我们想改变虚函数原型,我们必须改变derived-s的每个声明吗?

否.如果要更改基类中的虚函数原型,则需要更改公共接口.你不应该这样做.

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

protected成员应被视为类的公共接口的一部分.它protected可以帮助您避免因使用它们而导致的风险public.但不要搞错:protected成员是类接口的一部分,应该这样对待.

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

foo(Bacon b)
{
    b.cook();
}
Run Code Online (Sandbox Code Playgroud)

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

更新

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

每一段知识都必须在系统中具有单一,明确,权威的表示.

在相关类中声明虚函数并不违反此原则.它们的功能不同.在Base,你说的Base::foovirtual.在Derived,你说的Derived::foo也是virtual.Base::foo并且Derived::foo是两个独立的函数,但恰好可以通过指针或对a的引用来调用它们Base.

  • +1.需要包含私有函数的另一个原因是`friend`声明的可能性.完全可以在仅包含头的.cpp文件中实现友元函数.编译该.cpp文件时,编译器需要能够检查私有成员函数的函数调用的正确性. (3认同)
  • 除了朋友之外,内联函数还需要知道整个类的定义. (2认同)

Dav*_*eas 5

这不是愚蠢的,因为如果我们想改变虚函数原型,我们必须改变derived-s的每个声明吗?

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

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

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

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

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; }();
Run Code Online (Sandbox Code Playgroud)

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

std::unique_ptr<int[]> Base::member(new int[10]());
Run Code Online (Sandbox Code Playgroud)