在使用Nifty Counter C++Idiom时,必须调用构造函数两次
In using the Nifty Counter C++ Idiom, must the constructor be called twice?
当试图用Nifty Counter C++Idiom初始化静态成员时,我遇到了一些问题。你能解释一下在以下情况下如何正确使用ideom吗?
问题似乎如下:我在不同的编译单元中有两个静态对象,其中一个使用另一个(CDataFile
)的静态成员。因为本例中的初始化顺序没有定义,而且在生产环境中,它的顺序是错误的,所以我尝试使用Nifty Counter习惯用法。但现在CDataFile
的静态成员似乎初始化了两次(构造函数调用了两次)。第一次,构造函数在CDataFileInitializer
中被调用,这很好。之后使用静态成员(mSome
被填充),但随后CSomeClass
的构造函数被第二次调用,并且mSome
的内容被清除。
// datafile.h
class CDataFile
{
friend class CDataFileInitializer;
protected:
static CSomeClass mSome;
// other code
};
static class CDataFileInitializer
{
public:
CDataFileInitializer();
} dfinitializer;
// datafile.cpp
static int nifty_counter;
CSomeClass CDataFile::mSome; // second initialization comes from here?
CDataFileInitializer::CDataFileInitializer()
{
if (!nifty_counter++)
{
printf("CDataFileInitializer Constructorn");
CDataFile::mSome= CSomeClass(); // first initialization
}
}
行:
CSomeClass CDataFile::mSome;
定义CSomeClass类型的变量。此变量的初始化分为两个阶段:首先,它被初始化为零,这(大约)意味着它所在的内存全部设置为0。之后,将进行动态初始化。这将导致其构造函数运行。
dfinitializer
遵循类似的"零初始化然后动态初始化"模式。在其动态初始化步骤中,它在CDataFile::mSome
上调用operator=
,以便将新的默认构造的CSomeClass()
分配给mSome
。
这一步骤完全没有意义,因为mSome
和dfinitializer
的动态初始化相对于彼此是不确定的。如果dfinialiser
首先被初始化,它将尝试分配给尚未创建的对象(稍后将默认构造),如果它第二次被初始化,则它将重新分配给已经创建的对象。
代替:
CSomeClass CDataFile::mSome;
您应该创建一个可以在其中构造对象的存储区域:
alignas(CSomeClass) unsigned char CDataFile::mSome[sizeof(CSomeClass)];
然后将CDataFileInitializer
更改为:
CDataFileInitializer::CDataFileInitializer()
{
if (!nifty_counter++)
{
printf("CDataFileInitializer Constructorn");
new (&CDataFile::mSome) CSomeClass();
}
}
另一种选择是使用函数静态变量:
CSomeClass& getMSome() {
static CSomeClass mSome;
return mSome;
}
这将以线程安全的方式延迟初始化mSome
。
如果在命名空间范围内定义对象,其构造函数将为在初始化期间的某个时刻由启动代码调用。如果你想要使用漂亮的反成语,你需要以某种方式抑制它,或者让它成为一个no-op。您还必须在实际初始化器。有几种方法可以实现这一点:
-
我所见过的大多数工业实力的实现要么在汇编程序中声明对象,或者使用编译器扩展来确保构造函数没有被调用。这不是很便携,但是对于iostream这样无论如何都无法在纯C++中实现的东西,这通常是可以接受的。(事实上,这是唯一可以接受的解决方案对于iostream对象,因为它们不允许被破坏。)
-
我通常会安排一个特殊的无操作构造函数什么也不做。从形式上讲,它不能保证有效,但在实践中,确实如此。然后定义使用漂亮的实例使用此构造函数的反习惯用法。
-
最后,如果您控制正在构建的类,并且实例由漂亮计数器控制,如果它可以有琐碎的构造函数,您不需要处理构造函数,只需初始化初始化器中的各种成员。
这些都不是特别好的解决方案,在新代码中,我会使用singleton习语的变体。
yes-an-no:yes它被调用两次,no在两个不同的对象上被调用。
假设你有
// A.cpp
#include "datafile.h"
...
和
// B.cpp
#include "datafile.h"
...
由于#include,A.cpp和B.cpp都将具有dfinitializer
的本地和独立副本。
datafile.cpp依次具有nifty_counter
(最好用初始值0来定义它…static int nifty_counter = 0;
)和CDatafile::mSome(在文件级初始化)。
CDataFileInitializer
ctor所做的是为已经初始化的mSome
分配临时的CSomeClass(),该CSomeClass在运行中创建和销毁。
事实上,所有这些都是做正确事情的错误实现,只是因为CDataFile是可赋值的。
如果问题只是初始化静态数据成员,那么您所要做的就是确保包含静态成员定义(注意:定义,而不是声明)的模块中的某些内容被其他模块调用,从而在模块中产生一些副作用(只是为了避免被优化)
所以。。。让我们试试更好的
//some.h
#ifndef SOME_H_INCLUDED
#define SOME_H_INCLUDED
#include<iostream>
class CSome
{
public:
CSome() { std::cout << "CSome["<<this<<"] default created" << std::endl; }
CSome(const CSome& s) { std::cout << "CSome["<<this<<"] created from ["<<&s<<"]" << std::endl; }
CSome& operator=(const CSome& s) { std::cout << "CSome["<<this<<"] assigned from ["<<&s<<"]" << std::endl; return *this; }
CSome(CSome&& s) { std::cout << "CSome["<<this<<"] created moving ["<<&s<<"]" << std::endl; }
CSome& operator=(CSome&& s) { std::cout << "CSome["<<this<<"] assigned moving ["<<&s<<"]" << std::endl; return *this; }
~CSome() { std::cout << "CSome["<<this<<"] destroyed" << std::endl; }
};
#endif // SOME_H_INCLUDED
//datafile.h
#ifndef DATAFILE_H_INCLUDED
#define DATAFILE_H_INCLUDED
#include "some.h"
class CDataFile
{
public:
protected:
static CSome mSome;
};
static class CDataFileInitializer
{
public:
CDataFileInitializer();
~CDataFileInitializer();
} datafileinitializer;
#endif // DATAFILE_H_INCLUDED
//datafile.cpp
#include "datafile.h"
#include <iostream>
static int nifty_counter = 0; //the one and only
CSome CDataFile::mSome; //define and initialize
CDataFileInitializer::CDataFileInitializer()
{
std::cout << "CDataFileInitializer["<<this<<"] creation"<< std::endl;
if(!nifty_counter++)
{
std::cout << "CDataFileInitializer FIRST INITIALIZATION"<< std::endl;
}
}
CDataFileInitializer::~CDataFileInitializer()
{
std::cout << "CDataFileInitializer["<<this<<"] destruction"<< std::endl;
if(!--nifty_counter)
{
std::cout << "CDataFileInitializer LAST DESTRUCTION"<< std::endl;
}
}
//A.cpp
#include <iostream>
static class A
{
public:
A() { std::cout << "initializing A.cpp" << std::endl; }
~A() { std::cout << "cleaning A.cpp" << std::endl; }
} a;
#include "datafile.h"
// other a.cpp code ...
void call_a() { std::cout << "do something in a.ccp" << std::endl; }
//B.cpp
#include <iostream>
static class B
{
public:
B() { std::cout << "initializing B.cpp" << std::endl; }
~B() { std::cout << "cleaning B.cpp" << std::endl; }
} b;
#include "datafile.h"
// other b.cpp code ...
void call_b() { std::cout << "do something in b.ccp" << std::endl; }
//main.cpp
#include <iostream>
void call_a();
void call_b();
int main()
{
std::cout << "main" << std::endl;
call_a();
call_b();
std::cout << "main return" << std::endl;
return 0;
}
将给出以下输出:
CDataFileInitializer[0x406035] creation
CDataFileInitializer FIRST INITIALIZATION
CSome[0x40602c] default created
initializing A.cpp
CDataFileInitializer[0x406029] creation
initializing B.cpp
CDataFileInitializer[0x406025] creation
main
do something in a.ccp
do something in b.ccp
main return
CDataFileInitializer[0x406025] destruction
cleaning B.cpp
CDataFileInitializer[0x406029] destruction
cleaning A.cpp
CSome[0x40602c] destroyed
CDataFileInitializer[0x406035] destruction
CDataFileInitializer LAST DESTRUCTION
当然,地址会根据您的机器和运行情况而变化。
- 获取从C++中同一类中的构造函数调用的方法返回的值
- 从具有按值捕获的 lambda 移动构造 std::函数时,移动构造函数调用两次
- 确保所有构造函数调用相同的函数 c++ 设计模式
- 减少复制构造函数调用
- 使用回调函数从构造函数调用虚拟/派生方法的替代方法?
- 在 Google 测试中,我可以从构造函数调用 GetParam() 吗?
- C++ - 从另一个类构造函数调用类构造函数
- 在C++中初始化带有和不使用构造函数调用的对象有什么区别
- 是否可以从移动构造函数调用默认构造函数?
- 在模板生成器模式中分解重复的构造函数调用
- std::atexit 从全局对象的构造函数调用时的排序
- 对构造函数调用的约束
- 编译器错过了无效的构造函数调用,并调用不存在的(或私有的)默认构造函数
- 用构造函数调用填充向量
- 创建指针时是否没有构造函数调用
- 使用 emplace_back 避免移动构造函数调用的最佳方法?
- C++ 抽象类构造函数调用
- 为什么函数参数将带有参数的构造函数调用
- 为什么比“构造函数”调用更多的“解构器”调用
- 将对象传递给函数并不是导致构造函数调用