C++样式:在构造函数主体中使用参数或成员
C++ style: use parameter or member in constructor body
这是我经常遇到的问题,最后想听听人们对自己喜欢的风格的看法。
在构造函数中使用(出于只读目的)参数或成员是否更好/首选做法?例如,在这个简单的向量类中:
#include <iostream>
#include <array>
class SimpleDoubleVector {
private:
double * _data;
std::size_t _size;
public:
SimpleDoubleVector(double * data, std::size_t size) :
_size(size) {
_data = new double[size];
for (int k=0; k<size; ++k)
_data[k] = data[k];
}
~SimpleDoubleVector() {
delete[] _data;
}
};
是不是更好
- 在整个构造函数中使用
size
(如图所示)或 - 首先分配/初始化
_size
,然后使用_size
可能的后果:
哪个更具可读性?
哪个会提供更好的性能(或者由于复制传播,它们都是等效的)?直观地说,感觉从参数读取会更有效率,因为它永远不会被写入,因此会产生更简单的依赖关系图。
我知道这可能看起来很迂腐,但它经常出现,我真的很想剖析最好的方法(或者至少更好地了解利弊是什么)。
从语义上讲,局部变量(以及参数)通常优先于成员变量。举这个有点捏造的例子:
class Complex {
float real_;
float imag_;
public:
Complex& operator*=(const Complex& that) {
real_ = real_ * that.real_ - imag_ * that.imag_;
imag_ = imag_ * that.real_ + real_ * that.imag_;
}
};
乍一看很好,直到您意识到您在第一行中对real_
的修改改变了您在第二行中real_
的值。即使您捕获了它并将原始real_
存储在局部变量中,也可能是在 c *= c
的情况下,其中运算符的左侧和右侧是锯齿,并且您在第一行中更改real_
无意中改变了第二行中的that.real_
。换句话说,对成员变量的更改能够引起对局部变量的更改不会产生的副作用。
在速度方面,任何合理的编译器都会发现两者相同。如果重用该参数,不合理的编译器可能会生成更好的代码,因为它已经在本地,并且编译器肯定知道除了它可以看到的代码之外,没有什么可以更改该值。还值得注意的是,即使是在好的编译器上,稍微复杂的情况也可能产生更差的输出:
void MyClass::foo(int value, MyClass* child) {
value_ = value;
for (int i = 0; i < value_; ++i) {
if (child) child->value_ = i;
bar(i, child);
}
}
此函数绝对无法保证this
和child
是不同的指针。因此,它不能在循环迭代之间将value_
保留在寄存器中,因为对child->value_
的分配可能会更改this->value_
。在这种情况下,即使是优秀的编译器也会希望看到您使用该参数。
可读性方面,如果您认为成员名称(或m_
)之前或之后的下划线使其不可读,那么您为什么要使用该表示法?构造函数体和正常函数体之间的一致性绝对是可取的。因此,我认为,如果您的语义鼓励在函数持续时间内将成员变量拉入局部变量,那么也可以在构造函数中执行此操作(只需使用参数)。但是,如果其他成员函数中没有使用此类约定,则也不要在构造函数中执行此操作 - 让编译器来处理它。
我总是在整个构造函数中使用参数(如果可能),原因有两个:
1)我正在从外部输入初始化对象状态。使用参数强调了对外部数据的这种使用。
2) 当更广泛地使用初始值设定项列表时,它可以防止在初始化之前使用类成员的各种问题(由于初始化顺序由成员顺序指定,而不是构造函数中的初始值设定项顺序)。
我想不出任何性能原因会使一个与另一个明显不同,所以只有当探查器告诉我改变它会导致显着改进时,我才会选择不同的方法。
我会按如下方式制作类:
class SimpleDoubleVector {
private:
std::size_t _size; // Make sure this is declared first!!
double * _data;
public:
SimpleDoubleVector(double * data, std::size_t size) :
_size(size), data(new double[size]) // Use initialization lists
{
for (int k = 0; k < _size; ++k) // Could eliminate all this with std::vector
_data[k] = data[k];
}
~SimpleDoubleVector() {
delete[] _data;
}
};
当然,这不是全部代码,因为您正在管理资源,因此您需要实现三法则(或 5 或 C++11 中的类似规则)。但是,有几点提示:
当你在构造函数的主体中
_size = size;
时,你不再做初始化,你正在做赋值,这就是为什么你应该使用初始化列表(当然对于内置类型,这实际上是一回事,但是,我认为意图是不同的)。传递给构造函数的参数用于初始化示例中的成员变量。除了执行初始化之外,不应将它们用于任何其他目的。
您可能最好使用
std::vector<double>
或std::array<double>
,但我相信这与问题无关。
另外,我不知道依赖关系图与这个问题有什么关系。
(个人说明:我从来不喜欢成员变量的前缀_
样式)
如果问题是可读性,答案应该是初始值设定项列表。由于_data
列在第一位,因此该问题是被迫的。
SimpleDoubleVector(double * data, std::size_t size)
: _data(std::copy(data, data+size, new double[size])),
_size(size)
{}
如果首先列出_size
,则有一个选择,但在这种情况下我会选择使用该参数,因为没有_
源代码会稍微容易阅读。我相信有了std::copy
,性能差异可以忽略不计。
如果初始化必须在构造函数的主体中进行,如果参数和数据成员名称中的名称具有 1-1 的对应关系,我将使用相同的推理。如果数据成员是使用某种参数计算初始化的,那么很明显,如果计算值对其他数据成员的初始化有用,则代码应该使用计算值。如果存在复杂的初始化,将该初始化放在单独的函数中通常很有用。这预期了多个构造函数。可以编写此函数以利用初始化的数据成员,以便最小化构造函数和初始化函数之间传递的参数。
SimpleDoubleVector(double * data, std::size_t size) {
_size = size;
initialize_data(data);
}
SimpleDoubleVector(std::size_t size) {
_size = size;
initialize_data();
}
double * initialize_data(double * data = 0) {
_data = new double[_size];
if (data) {
for (std::size_t k = 0; k < _size; ++k) {
_data[k] = data[k];
}
}
}
- 基于另一个成员参数将函数调用从类传递给它的一个成员
- 使用指向成员的指针将成员函数作为参数传递
- 如何将lambda作为模板类的成员函数参数
- 将成员函数指针作为参数传递给模板方法
- c++构造函数成员初始化:传递参数
- 使用带有 ref 参数的成员函数创建线程时出现编译错误
- 如何将成员函数作为回调参数传递给需要"typedef-ed"自由函数指针的函数?
- 模板化检查是否存在带有参数列表的类成员函数?
- 如何将类成员方法的参数列表自动填充写入可变参数?
- 为什么我需要在成员发起器列表中重复基类的模板参数?
- C++向量默认为成员参数
- 将成员函数作为构造函数参数调用时出错 "Variable is not a type name"
- 可变参数模板参数扩展 类型为 std::function 的类成员
- 绑定到可变参数成员函数
- C/C++ 包含点的宏参数(成员访问运算符)
- 将const char * const参数成员分配给新值
- 基于模板参数成员函数参数的模板专用化
- 如何访问可变参数成员
- 可变类参数成员变量的异构存储
- 非模板类中的可变参数成员