这是我的第一篇文章。我相信我知道stackoverflow的最佳实践,但可能不是100%。我相信没有具体的帖子可以解决我的讯问;我也希望它不会太模糊。
我试图找出编写C ++构造函数的好习惯 从事中到重型工作。
将(全部?)init工作推入初始化列表似乎是一个好主意 我想到了两个原因,即:
资源获取是初始化
据我所知,保证会员的最简单方法 在资源获取时正确初始化是为了确保 初始化列表的括号内的内容是正确的 评估时。
class A
{
public:
A(const B & b, const C & c)
: _c(c)
{
/* _c was allocated and defined at the same time */
/* _b is allocated but its content is undefined */
_b = b;
}
private:
B _b;
C _c;
}
const
班级成员
使用初始化列表是唯一正确的使用方法
可以保存实际内容的const
个成员。
class A
{
public:
A(int m, int n, int p)
: _m(m) /* correct, _m will be initialized to m */
{
_n = n; /* incorrect, _n is already initialized to an undefined value */
*(const_cast<int*>(&_p)) = p; /* technically valid, but ugly and not particularly RAII-friendly */
}
private:
const int _m, _n, _p;
}
但是有些问题似乎会影响初始化列表的使用:
顺序
成员变量总是按照它们在类定义中声明的顺序进行初始化,因此在构造函数初始化列表中按顺序写入它们。以不同的顺序编写它们只会使代码混乱,因为它不会按照您看到的顺序运行,这会使得很难看到依赖于顺序的错误。
http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#S-discussion
如果使用值初始化值,这很重要 先前在列表中初始化。例如:
A(int in) : _n(in), _m(_n) {}
如果在_m
之前定义_n
,则初始化时的值未定义。
我准备在我的代码中应用此规则,但在工作时 与其他人一起导致代码冗余并强制阅读 一次两个文件。 这是不可接受的,并且有些容易出错。
解决方案 - 仅使用来自ctor参数的数据进行初始化。
解决方案的问题 - 在没有的情况下保持初始列表中的工作 内在依赖意味着重复操作。例如:
int in_to_n(int in)
{
/* ... */
return n;
}
int modify(int n)
{
/* ... */
return modified;
}
A::A(int in)
: _n(in_to_n(in))
, _n_modified(modify(in_to_n(in)))
{}
对于一些重复操作,我相信编译器 可以重复使用现有数据,但我认为不应该依赖于此 对于重要的工作(我甚至不认为如果打电话就完成了 非内联单独代码)。
你可以在列表中放多少工作?
在前面的例子中,我调用函数来计算它的内容 属性将被初始化为。这些可以是普通/ lambda 函数或静态/非静态方法, 当前班级或其他班级。 (我不建议使用当前班级的非静态方法, 根据标准,它甚至可能是未定义的用法,不确定。)
我想这本身并不是一个大问题,但需要制作 特别努力清晰,以保持代码的意图,如果 写那些以这种方式做大工作的大班。
此外,在尝试将解决方案应用于上一个问题时, 在初始化时,你可以做很多独立的工作 你的实例......如果你有一个很长的序列,这通常会变大 用内部依赖项初始化的属性。 它开始看起来只是程序,翻译成一个 初始化列表;我想这不是C ++应该是什么 过渡到?
多个插件
通常会同时计算两个变量。设置两个变量 在init列表中立即表示:
使用丑陋的中间属性
struct InnerAData
{
B b;
C c;
};
/* must be exported with the class definition (ugly) */
class A
{
public:
A(const D & input)
: _inner(work(input))
, _b(_inner.b)
, _c(_inner.c) {}
private:
B _b;
C _c;
InnerAData _inner;
}
这太糟糕了,并且会强制使用额外无用的副本。
或一些丑陋的黑客
class A
{
public:
A(const D & input) : _b(work(input)) {}
private:
B _b;
C _c;
B work(const D & input)
{
/* ... work ... */
_c = ...;
}
}
这更加糟糕,甚至无法使用const
或非内置类型属性。
保留内容const
有时可能需要大部分人来确定价值
给一个属性,以确保它是const
,
因此将工作移至初始化列表,
似乎受到限制。我没有举一个完整的例子,但想一想
比如从默认文件名计算数据
从该数据计算完整的文件名,然后检查是否
存在相应的文件来设置const boolean等。
我认为这不是一个根本问题,但似乎只是一切 直观地在ctor的身体中更清晰,并且移动 它只是为了正确的初始化而在init列表中 const领域似乎有点矫枉过正。也许我只想象事物。
所以这是困难的部分:提出具体问题! 你遇到过类似的问题吗,你找到了更好的解决方案, 如果不是要学习的课程 - 或者是我有什么东西 在这里失踪?
我想我的问题是我几乎想要完成所有的工作 当我可以搜索什么状态的妥协时,到init列表 已启动,稍后会留下一些工作。我只是觉得初始列表 可以在制作现代C ++代码方面发挥更大的作用 我还没有看到它们比基本用法更进一步。
此外,我真的不相信为什么值 按该顺序初始化,而不是按列表的顺序。 我已经口头告诉它了,因为堆栈上的属性是有序的 编译器必须保证堆栈数据永远不会高于SP。 我不确定这是怎样的最终答案......非常确定可以 实现安全的任意重新排序的初始化列表, 如果我错了,请纠正我。
答案 0 :(得分:0)
在您的代码中:
class A
{
public:
A(const B & b, const C & c)
: _c(c)
{
/* _c was allocated and defined at the same time */
/* _b is allocated but its content is undefined */
_b = b;
}
private:
B _b;
C _c;
}
构造函数调用B::B()
然后调用B::operator=
这可能是一个问题,如果其中任何一个不存在,是昂贵的或没有正确实现RAII和三个规则准则。经验法则是,如果可能的话,总是更喜欢初始化列表。
答案 1 :(得分:0)
从c ++ 11开始,另一种方法是使用委托构造函数:
struct InnerData;
InnerData (work(const D&);
class A
{
public:
A(const D & input) : A(work(input)) {}
private:
A(const InnerAData&);
private:
const B _b;
const C _c;
};
并且(可以内联,但在标题中可见)
struct InnerAData
{
B b;
C c;
};
A::A(const InnerAData& inner) : _b(inner.b), _c(inner.c) {}