单例模式:auto_ptr和unique_ptr的不同行为

时间:2013-04-23 19:42:54

标签: c++ static linker singleton auto-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

一切都按预期工作,我得到以下内容到stdout:

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_ptrunique_ptr替换,则行为始终与我期望的正确行为一致。

这让我想到了一个问题:有没有人知道这里发生了什么?

2 个答案:

答案 0 :(得分:4)

未指定构建dummystd::auto_ptr< singleton > singleton::ptr_(0)的顺序。

对于auto_ptr案例,如果您构建dummy然后singleton::ptr_(0)dummy调用中创建的值将被ptr_(0)的构造函数删除。

我会通过ptr_或类似内容向ptr_(([](){ std::cout << "made ptr_\n"; }(),0));的构造添加跟踪。

它与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()

答案 1 :(得分:3)

未指定在不同翻译单元中定义的文件范围对象的构造顺序。然而,通常,在第二翻译单元中定义的对象之前构造在另一翻译单元之前链接的翻译单元中定义的对象。这里的区别在于a.osingleton.o的关联顺序。在singleton.o之前链接a.o时,singleton::ptr_会在dummy之前初始化,并且一切正常。首先链接a.o时,首先初始化dummy,然后构造单例;然后singleton::ptr_被初始化为0,丢弃指向singleton原始副本的指针。然后在对foo的调用中,对singleton::get()的调用再次构建了单例。