如何让 C++ 与 DRY 完美契合? [关闭]
Posted
技术标签:
【中文标题】如何让 C++ 与 DRY 完美契合? [关闭]【英文标题】:How to make C++ fits well with DRY? [closed] 【发布时间】:2013-07-23 02:06:09 【问题描述】:在 C++ 中,如果我们在某个基类(比如 Base)中有一些虚函数,我们想覆盖这个虚函数,我们必须再次声明这个虚函数以使其在派生类中也能工作.
class Base
public:
virtual void virtualFunction();
static int s_whatSoEver[];
private:
void _privateFunction();
class Derived
public:
virtual void virtualFunction();
这不是很愚蠢吗,因为如果我们想要更改虚函数原型,就必须更改派生-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;
并专门为其初始化一个静态类常量:
static BaseStaticVariableInitializer s_baseStaticVariableInitializer;
对不起,我的无知,但是您编写 C++ 代码以适应 DRY 的正确方法是什么?
【问题讨论】:
一篇文章有很多问题.... 我肯定会把关于静态初始化的问题视为一个单独的问题。 (部分回答这个问题,C++11 统一初始化将帮助您大大简化这一点。) 顺便说一句,delete
是 new[]
-ed 的未定义行为。你应该delete[]
那段记忆。
@DavidRodríguez-dribeas 感谢您的更正,我已经更正了。
【参考方案1】:
这不是很愚蠢吗,因为如果我们想更改虚函数原型,就必须更改派生-s 的每个声明?
没有。如果您要更改基类中的虚函数原型,则您正在更改公共接口。你不应该那样做。
另外,为什么要在头文件中声明一些受保护或私有的函数,因为头文件是用来定义公共接口的,使用这个接口的用户永远不需要关心它们有吗?
protected
成员应被视为类的公共接口的一部分。 protected
只是帮助您避免错误,否则您可能会因为拥有它们public
而冒险。但请不要搞错:protected
成员是类接口的一部分,应该这样对待。
关于头文件中的private
s:是的,我同意,在许多方面将它们仅保留在实现文件中会更合乎逻辑。但是,请考虑何时按值传递类:
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。需要包含私有函数的另一个原因是friend
声明的可能性。在仅包含标头的 .cpp 文件中实现友元函数是完全可能的。当编译该.cpp文件时,编译器需要能够检查私有成员函数的函数调用的正确性。
@david.wan 我不明白怎么...?如果您在A.h
中定义的类中有一个私有函数,并且该类有一个在B.cpp
中实现的友元函数,而B.cpp
有一个#include "A.h"
并调用私有成员,编译器如何处理如果未在 A.h
中声明私有成员?
除了朋友,内联函数还需要知道整个类的定义。
@jogojapan 在这种情况下,我们必须保留在“A.h”中声明的私有函数。很抱歉,将函数声明和定义保留在 Objective-c 的实现文件中是可行的。显然,Objective-C 中没有“朋友”之类的东西。【参考方案2】:
这不是很愚蠢吗,因为如果我们想要更改虚函数原型,就必须更改派生-s 的每个声明?
这正是重点。如果基中的签名发生更改,您希望编译器告诉您,而不是潜在地尝试使用错误类型调用函数。 C++ 是一种静态类型的编译语言。类型是在编译时定义的,如果虚函数的类型发生变化,你希望重新编译以适应变化。
为什么要在头文件中声明一些受保护或私有的函数,因为头文件是用来定义公共接口的,而使用这个接口的用户根本不需要关心它们?
这又是一个完全相同的设计选择。在 C++ 中,单一定义规则要求每种类型在 all 翻译单元(不同的编译文件)中定义完全相同。如前所述,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]());
【讨论】:
这是我在这里看到的唯一正确答案【参考方案3】:快速简短的回答。 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];
【讨论】:
感谢您指出我对重载和覆盖和覆盖的误解,我已经纠正了它。您已经在那里定义了 s_whatSoEver,但是您初始化它们的地方在哪里?此外,如果静态类变量是某个用户定义的类怎么办? @david.wan 欢迎您 Dave,在 C++ 中,除了声明之外,您还必须定义它。最多只能在一个翻译 uniot 中完成 o=done。[.cpp 文件] 如果它的 Simple 变量或具有构造函数,您可以在定义它时进行初始化。否则你可以像我在答案中所做的那样在静态函数中做到这一点【参考方案4】:为什么需要在头文件中声明一些受保护或私有的函数? ...
编写适合 DRY 的 c++ 代码的正确方法是什么?`
您可以使用Pimpl idiom 隐藏头文件中的实现细节(私有成员和方法签名的声明)。此外,您将获得更快的构建速度。 Qt 广泛使用这个习语。
【讨论】:
以上是关于如何让 C++ 与 DRY 完美契合? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章