私有类成员延迟初始化的最佳做法
Best practice for deferred initialization of private class members
是否有延迟初始化私有类成员M
类C
的最佳实践?例如:
class C {
public:
C();
// This works properly without m, and maybe called at any time,
// even before startWork was called.
someSimpleStuff();
// Called single time, once param is known and work can be started.
startWork(int param);
// Uses m. Called multiple times.
// Guaranteed to only be called after startWork was called
doProcessing();
private:
M m;
};
class M {
M(int param);
};
无法构造类 C
的对象,因为M
没有默认的初始值设定项。
如果可以修改M
的实现,则可以在M
中添加一个init
方法,并使其构造函数不接受任何参数,这将允许构造类C
的对象。
如果没有,你可以将C
的成员m
包装在std::unique_ptr
中,并在可能的情况下构造它。
但是,这两种解决方案都容易出现在运行时会捕获的错误。是否有一些做法可以在编译时确保m
仅在初始化后使用?
限制:C 类的对象被传递给使用其公共接口的外部代码,因此 C 的公共方法不能拆分为多个类。
最佳做法是永远不要使用延迟初始化。
在您的情况下,放弃 C
的默认构造函数并将其替换为 C(int param) : m(param){}
。也就是说,类成员在构造点使用基本成员初始化进行初始化。
使用延迟初始化意味着您的对象可能处于未定义状态,并且实现并发等目标更加困难。
#define ENABLE_THREAD_SAFETY
class C {
public:
C();
// This works properly without m, and maybe called at any time,
// even before startWork was called.
someSimpleStuff();
// Called single time, once param is known and work can be started.
startWork(int param);
// Uses m. Called multiple times.
// Guaranteed to only be called after startWork was called
doProcessing();
M* mptr()
{
#ifdef ENABLE_THREAD_SAFETY
std::call_once(create_m_once_flag, [&] {
m = std::make_unique<M>(mparam);
});
#else
if (m == nullptr)
m = std::make_unique<M>(mparam);
#endif
return m.get();
}
private:
int mparam;
std::unique_ptr<M> m;
#ifdef ENABLE_THREAD_SAFETY
std::once_flag create_m_once_flag;
#endif
};
class M {
M(int param);
};
现在你所要做的就是停止直接使用 m,而是通过 mptr(( 访问它。它只会在首次使用时创建一次 M 类。
我会选择unique_ptr...您认为这有什么问题?使用 M 时,您可以轻松检查:
if(m)
m->foo();
我知道这不是编译时检查,但据我所知,当前的编译器无法进行检查。代码分析必须非常复杂才能看到这样的东西,因为您可以随时以任何方法初始化m,或者 - 如果公共/受保护 - 甚至在另一个文件中。编译时检查意味着延迟初始化是在编译时完成的,但延迟初始化的概念是基于运行时的。
好的,根据我对你的问题的了解,这是一个解决方案吗?
您将不需要M
的功能放入class D
.您可以创建D
对象并使用它。一旦你需要M
并且你想做doProcessing()
代码,你创建C
的对象,D
传递给它,并使用你现在拥有的param
初始化它。
下面的代码只是为了说明这个想法。在这种情况下,您可能不需要startWork()
成为一个单独的函数,其代码可以在 C
的构造函数中编写
注意:我已将所有函数设置为空,因此我可以编译代码以检查语法错误:)
class M
{
public:
M(int param) {}
};
class D
{
public:
D() {}
// This works properly without m, and maybe called at any time,
// even before startWork was called.
void someSimpleStuff() {}
};
class C
{
public:
C(D& d, int param) : d(d), m(param) { startWork(param); }
// Uses m. Called multiple times.
// Guaranteed to only be called after startWork was called
void doProcessing() {}
private:
// Called single time, once param is known and work can be started.
void startWork(int param) {}
D& d;
M m;
};
int main()
{
D d;
d.someSimpleStuff();
C c(d, 1337);
c.doProcessing();
c.doProcessing();
}
问题是"是否可以在编译时检查 m 是否仅在初始化后才使用而不拆分 C 的接口?
答案是否定的,您必须使用类型系统来确保在初始化之前不使用对象 M,这意味着拆分 C 接口。在编译时,编译器只知道对象的类型和常量表达式的值。C 不能是文本类型。所以你必须使用类型系统:你必须拆分 C 接口,以确保在编译时 M 只在初始化后使用。
- 初始化数组、"memset"或" {//value} "的最佳方法是什么?
- 初始化不是整数的巨大常量多维数组的最佳方法是什么?
- 这是使用构造函数初始化数组对象的最佳方法吗?
- 初始化迭代器的最佳方法是什么?
- C 初始化列表std ::数组最佳性能
- 成员变量的初始化必须延迟。此类的最佳设计选择是什么?
- C++ - 在一个函数/文件中初始化变量然后在main()/另一个文件中使用它的最佳方法是什么?
- 初始化随机生成器的最佳位置
- 声明变量而不初始化变量的最佳实践,因此auto不可用
- 多列表初始化的 C++ 最佳做法
- 初始化 C++11 数组的最佳方法,主要是标识映射
- 私有类成员延迟初始化的最佳做法
- 在皮条类中初始化默认值的最佳位置
- 这是初始化值为std::向量的std::映射的最佳方法
- 类成员初始化的最佳实践
- 当顺序很关键时,初始化c++组件的最佳实践
- c++中创建和初始化数组的最佳方式
- c++复杂静态初始化逻辑的最佳实践
- 在共享库中初始化全局数据的最佳方法是什么?
- 初始化类成员的最佳方式