在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的正确方法是什么?
答案 0 :(得分:6)
这不是愚蠢的,因为如果我们想更改虚函数原型,我们必须更改derived-s的每个声明吗?
没有。如果要更改基类中的虚函数原型,则需要更改公共接口。你不应该这样做。
此外,为什么有必要在头文件中声明一些受保护或私有函数,因为头文件用于公共接口定义,并且使用此接口的用户永远不需要关心它们在所有?
protected
成员应被视为类的公共接口的一部分。 protected
只是帮助您避免因使用public
而面临的风险。但不要搞错: protected
成员是班级界面的一部分,应该这样对待。
关于头文件中的private
:是的,我同意,在许多方面将它们仅保留在实现文件中更合乎逻辑。但是,请考虑何时按值传递类:
foo(Bacon b)
{
b.cook();
}
为了调用foo()
,您需要为编译器提供完整的Bacon
类定义。 (注意:只有类定义,而不是其成员函数的定义。)这样,编译器可以在调用foo()
时知道为类分配多少堆栈空间。如果编译器也必须搜索实现文件以查找private
变量,那么解析会更复杂(并且编译可能会更慢)。
既然你提到了DRY,我必须指出这一点。 DRY原则指出:
每一条知识都必须在系统中具有单一,明确,权威的表示。
在相关类中声明虚函数并不违反此原则。它们功能不一样。在Base
中,您声明Base::foo
为virtual
。在Derived
中,您声明Derived::foo
也是virtual
。 Base::foo
和Derived::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广泛使用这个成语。