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

时间:2013-07-23 02:06:09

标签: c++ dry

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

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

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

此外,为什么有必要在头文件中声明一些受保护或私有函数,因为头文件用于公共接口定义,并且使用此接口的用户根本不需要关心它们?也许我们可以直接实施&声明.cpp文件中的private或protected函数就像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 个答案:

答案 0 :(得分:6)

  

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

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

  

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

protected成员应被视为类的公共接口的一部分。 protected只是帮助您避免因使用public而面临的风险。但不要搞错: protected成员是班级界面的一部分,应该这样对待。

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

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

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

更新

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

  

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

在相关类中声明虚函数并不违反此原则。它们功能不一样。在Base中,您声明Base::foovirtual。在Derived中,您声明Derived::foo也是virtualBase::fooDerived::foo是两个独立的函数,但恰好可以通过指针或对Base的引用来调用它们。

答案 1 :(得分:5)

  

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

这正是重点。如果签名在基础中发生更改,您希望编译器告诉您,而不是可能尝试使用错误的类型调用该函数。 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]());

答案 2 :(得分:1)

快速简短回答。 1]为你的第一次qustion看到重写和amp;之间的差异在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];

答案 3 :(得分:1)

  

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

     

编写c ++代码以正确使用DRY的正确方法是什么?`

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