单例模式:auto_ptr和unique_ptr的不同行为
Singleton pattern: different behavior of auto_ptr and unique_ptr
在实现工厂类时,我遇到了我无法理解的std::auto_ptr
的行为。我把这个问题简化成下面这个小程序,所以…我们开始吧。
考虑以下单例类:
singleton.h
#ifndef SINGLETON_H_
#define SINGLETON_H_
#include<iostream>
#include<memory>
class singleton {
public:
static singleton* get() {
std::cout << "singleton::get()" << std::endl;
if ( !ptr_.get() ) {
std::cout << &ptr_ << std::endl;
ptr_.reset( new singleton );
std::cout << "CREATED" << std::endl;
}
return ptr_.get();
}
~singleton(){
std::cout << "DELETED" << std::endl;
}
private:
singleton() {}
singleton(const singleton&){}
static std::auto_ptr< singleton > ptr_;
//static std::unique_ptr< singleton > ptr_;
};
#endif
singleton.cpp
#include<singleton.h>o
std::auto_ptr< singleton > singleton::ptr_(0);
//std::unique_ptr< singleton > singleton::ptr_;
这里使用智能指针来管理资源主要是为了避免程序退出时的泄漏。我在下面的程序中使用了这段代码:
a.h
#ifndef A_H_
#define A_H_
int foo();
#endif
a.cpp
#include<singleton.h>
namespace {
singleton * dummy( singleton::get() );
}
int foo() {
singleton * pt = singleton::get();
return 0;
}
main.cpp
#include<a.h>
int main() {
int a = foo();
return 0;
}
现在是有趣的部分。我分别编译了这三个源代码:
$ g++ -I./ singleton.cpp -c
$ g++ -I./ a.cpp -c
$ g++ -I./ main.cpp -c
如果我按以下顺序显式链接它们:
$ g++ main.o singleton.o a.o
一切都按照我的预期工作,并且我得到以下标准输出:
singleton::get()
0x804a0d4
CREATED
singleton::get()
DELETED
如果我使用以下顺序链接源:
$ g++ a.o main.o singleton.o
我得到这样的输出:
singleton::get()
0x804a0dc
CREATED
singleton::get()
0x804a0dc
CREATED
DELETED
我尝试了不同的编译器品牌(Intel和GNU)和版本,这种行为在它们之间是一致的。无论如何,我无法看到其行为取决于链接顺序的代码。
此外,如果auto_ptr
被unique_ptr
取代,行为总是与我期望的正确行为一致。
这让我想到了一个问题:有人知道这里发生了什么吗?
dummy
和std::auto_ptr< singleton > singleton::ptr_(0)
的构造顺序未指定
对于auto_ptr
的情况,如果先构造dummy
然后再构造singleton::ptr_(0)
,则dummy
调用中创建的值将被ptr_(0)
的构造函数擦除。
我会通过ptr_(([](){ std::cout << "made ptr_n"; }(),0));
或类似的东西在ptr_
的构造中添加跟踪。
它与unique_ptr
一起工作的事实是巧合,可能是由于unique_ptr(0)
可以计算出它是零的优化,因为这样什么都不做(static
数据在构造开始之前被归零,所以如果编译器可以计算出unique_ptr(0)
只是将内存归零,它可以合法地跳过构造函数,这意味着您不再将内存归零)。
static std::auto_ptr< singleton >& get_ptr() {
static std::auto_ptr< singleton > ptr_(0);
return ptr_;
}
ptr_
的引用替换为get_ptr()
。在不同翻译单元中定义的文件作用域对象的构造顺序未指定。然而,典型地,在连接在另一个翻译单元之前的翻译单元中定义的对象在第二个翻译单元中定义的对象之前构造。这里的不同之处在于a.o
和singleton.o
链接的顺序。当singleton.o
在a.o
之前被链接时,singleton::ptr_
在dummy
之前被初始化,一切正常。当a.o
首先被链接时,dummy
首先被初始化,这就构造了单例;然后将singleton::ptr_
初始化为0,丢弃指向singleton
原始副本的指针。然后在对foo
的调用中,对singleton::get()
的调用再次构造单例。
- 在for循环中使用auto vs decltype(vec.size())来处理字符串的向量
- CLANG 编译器 说:变量"PTR"可能未初始化
- 如何在自定义类中启用'auto loops'?
- 当我在第一个循环中使用"auto"时,它工作正常,但是使用"int"它会给出错误,为什么?
- 在以唯一ptr为值的C++映射中,动态内存何时会被销毁
- 将 ptr 传递给 ptr 到 A 作为参数传递给 A 的函数是不好的做法吗?
- 为共享 ptr 向量实现复制 c'tor?
- 为什么当我为 for(auto& it : myUnorderedMap) {... = std::move(it.second)} 时,我会得到一个 const 引用?
- 字符和整数中 **(ptr+1) 的值差异
- C++:在不中断共享的情况下通过引用传递共享 PTR?
- 为什么结构化绑定不使用"auto&"返回对结构成员的引用,而是返回成员本身
- 擦除许多矢量元素,同时使用'auto'
- 考虑到其他好处,关键字'auto'真的有助于简化调试C++吗?
- 为什么"weak.lock()"返回"nullptr" "auto weak=std::make_shared<int>(42);"的定义?
- 推理类型如何工作"auto"和按引用调用?
- 如何将派生类从基 ptr 分配给 nlohmann::json
- 引用 std::shared:ptr 以避免引用计数
- 'auto *x = new some_struct{};"是一个未初始化的变量?
- 为什么 std::gcd/lcm 返回 std::common_type_t<M, N> 而不是 auto?
- 将函数参数类型声明为 auto