Weird behavior in GDB when defaulting copy/move constructors - c++

I have the following code which seems to behave weirdly in GDB depending if the copy/move constructors are defaulted or not.
#include <iostream>
#define CUSTOM 0
class Percentage
{
public:
using value_t = double;
Percentage() = default;
~Percentage() = default;
template <typename T>
Percentage(T) = delete;
Percentage(value_t value):
m_value(value)
{}
#if CUSTOM == 1
Percentage(const Percentage& p):
m_value(p.m_value)
{}
Percentage& operator=(const Percentage& p)
{
m_value = p.m_value;
return *this;
}
Percentage(Percentage&& p):
m_value(std::move(p.m_value))
{}
Percentage& operator=(Percentage&& p)
{
m_value = std::move(p.m_value);
return *this;
}
#else
Percentage(const Percentage&) = default;
Percentage& operator=(const Percentage&) = default;
Percentage(Percentage&&) = default;
Percentage& operator=(Percentage&&) = default;
#endif
friend std::ostream& operator<<(std::ostream& os, const Percentage& p)
{
return os << (p.m_value * 100.0) << '%';
}
private:
value_t m_value = 0.0;
};
struct test
{
Percentage m_p;
void set(const Percentage& v) { m_p = v; }
Percentage get() const { return m_p; }
};
int main()
{
test t;
std::cout << "Value 1: " << t.get() << std::endl;
t.set(42.0);
std::cout << "Value 2: " << t.get() << std::endl;
std::cout << "Breakpoint here" << std::endl;
}
I fire up GDB, add a breakpoint on the last cout in main and run "p t.get()" and I expect it to be 42 but depending on the value of the macro CUSTOM I get either 42 (when CUSTOM is 1) or 0 (when CUSTOM is 0).
What is happening ? Is this a bug in gdb, the compiler ?
OS: Fedora 26
Compiler: gcc 7.3.1
Flags: -fsanitize=address,leak -O0 -g3 -std=c++17
GDB 8.0.1-36

In general since the result of test::get is a "pure rvalue", the compiler is allowed to skip its initialization if its not bound to an lvalue (like a Percentage&&). So to see the content of the a pure rvalue you should store it in a Percentage&& variable, which "materializes" the prvalue and extends the lifetime of the temporary returned.
The difference between the 2 cases seems to exist in the executable (nothing related to GDB): one can see from the disassembly of the executable in the two cases that "test::get" differs, while if we compile with optimization on (-O3) the assembly generated is the same.
Case 0:
Percentage get() { return m_p; }
4009f0: 55 push %rbp
4009f1: 48 89 e5 mov %rsp,%rbp
4009f4: 48 89 7d f0 mov %rdi,-0x10(%rbp)
4009f8: 48 8b 7d f0 mov -0x10(%rbp),%rdi
4009fc: 48 8b 3f mov (%rdi),%rdi
4009ff: 48 89 7d f8 mov %rdi,-0x8(%rbp)
400a03: f2 0f 10 45 f8 movsd -0x8(%rbp),%xmm0
400a08: 5d pop %rbp
400a09: c3 retq
400a0a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
Case 1:
Percentage get() { return m_p; }
4009f0: 55 push %rbp
4009f1: 48 89 e5 mov %rsp,%rbp
4009f4: 48 83 ec 10 sub $0x10,%rsp
4009f8: 48 89 f8 mov %rdi,%rax
4009fb: 48 89 75 f8 mov %rsi,-0x8(%rbp)
4009ff: 48 8b 75 f8 mov -0x8(%rbp),%rsi
400a03: 48 89 45 f0 mov %rax,-0x10(%rbp)
400a07: e8 54 00 00 00 callq 400a60 <_ZN10PercentageC2ERKS_>
400a0c: 48 8b 45 f0 mov -0x10(%rbp),%rax
400a10: 48 83 c4 10 add $0x10,%rsp
400a14: 5d pop %rbp
400a15: c3 retq
400a16: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
400a1d: 00 00 00
Since you call test::get from GDB in this case you are not simply printing a value in memory, you are executing the lines above. You see there's a call to the copy constructor of Percentage, and the return of test::get seems to be in the rax register, while it seems that in the first snippet the implicit constructor is inlined, and the return value is stored in the floating point register xmm0.
I don't know why this difference (maybe someone expert in assembly can add some insight), but I suspect that's why GDB gets confused.

Related

Operator [] long and short versions

What is the advantage of using the longer version (something).operator[]() instead of simply (something)[]?
For example :
std::array<int, 10> arr1;
std::array<int, 10> arr2;
for(int i = 0; i < arr1.size(); i++)
std::cout << arr1[i] << ' ';
std::cout << std::endl;
for(int i = 0; i < arr2.size(); i++)
std::cout << arr2.operator[](i) << ' ';
std::cout << std::endl;
There is none. The [] is just syntactic sugar for operator[] on user-defined types. You only need the operator syntax when you define these functions yourself. This goes for all operators like operator(), operator[], operator new, operator=, ...
syntactic sugar
compiled with g++ -g -std=gnu++0x ...
0000000000400554 <main>:
#include <array>
int main() {
400554: 55 push %rbp
400555: 48 89 e5 mov %rsp,%rbp
400558: 48 83 ec 60 sub $0x60,%rsp
std::array<int, 10> arr1;
std::array<int, 10> arr2;
arr1[6];
40055c: 48 8d 45 d0 lea -0x30(%rbp),%rax
400560: be 06 00 00 00 mov $0x6,%esi
400565: 48 89 c7 mov %rax,%rdi
400568: e8 19 00 00 00 callq 400586 <std::array<int, 10ul>::operator[](unsigned long)>
arr2.operator[](6);
40056d: 48 8d 45 a0 lea -0x60(%rbp),%rax
400571: be 06 00 00 00 mov $0x6,%esi
400576: 48 89 c7 mov %rax,%rdi
400579: e8 08 00 00 00 callq 400586 <std::array<int, 10ul>::operator[](unsigned long)>
40057e: b8 00 00 00 00 mov $0x0,%eax
}
400583: c9 leaveq
400584: c3 retq
400585: 90 nop

Compile-time or Run-time evaluation of expression in constructor

I have the following class:
template<ItType I, LockType L>
class ArcItBase;
with a (one of them) constructor:
ArcItBase ( StableRootedDigraph& g_, Node const n_ ) noexcept :
srd ( g_ ),
arc ( I == ItType::in
? srd.nodes [ n_ ].head_in
: srd.nodes [ n_ ].head_out ) { }
The question is (which I don't see how to test) whether the value of the expression for the constructor of arc will be determined at compile-time or at run-time (Release, full optimization, clang-cl and VC14), given that I == ItType::in can be evaluated (is known, I is either ItType::in or ItType::out) at compile-time to either true or false?
It is not possible to have your code compiling without knowing the ItType at compile time.
The template parameter is evaluated at compile time and the conditional is a core constant expression, standard reference is C++11 5.19/2.
In the contrasting case the compiler would have to generate code that is equivalent to
arc(true ? : )
Which if you would actually write it would be optimized. However the rest of the conditional will not be optimized since you are accessing a what seems to be a non static member and cannot be evaluated as a core constant expression.
However, compilers may not always work as we expect so if you would actually want to test this you should dump the disassembled object file
objdump -DS file.o
and then you can better navigate the output.
Another option would be to launch the debugger and inspect the code.
Don't forget that you can always have your symbols even in case of optimizing, e.g.
g++ -O3 -g -c foo.cpp
Below you will find a toy implementation . In the first case values are given to the constructor of arcbase is called as:
arcbase<true> a(10,9);
Whereas in the second it is given non const random values that cannot be known at compile time.
After compiling with g++ --stc=c++11 -c -O3 -g the first case creates:
Disassembly of section .text._ZN7arcbaseILb1EEC2Eii:
0000000000000000 <arcbase<true>::arcbase(int, int)>:
srd isrd;
arc iarc;
public:
arcbase(int a , int b) : isrd(a,b) , iarc( I == true ? isrd.nodes.head_in : isrd.nodes.head_out ) {}
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 83 ec 10 sub $0x10,%rsp
8: 48 89 7d f8 mov %rdi,-0x8(%rbp)
c: 89 75 f4 mov %esi,-0xc(%rbp)
f: 89 55 f0 mov %edx,-0x10(%rbp)
12: 48 8b 45 f8 mov -0x8(%rbp),%rax
16: 8b 55 f0 mov -0x10(%rbp),%edx
19: 8b 4d f4 mov -0xc(%rbp),%ecx
1c: 89 ce mov %ecx,%esi
1e: 48 89 c7 mov %rax,%rdi
21: e8 00 00 00 00 callq 26 <arcbase<true>::arcbase(int, int)+0x26>
26: 48 8b 45 f8 mov -0x8(%rbp),%rax
2a: 8b 00 mov (%rax),%eax
2c: 48 8b 55 f8 mov -0x8(%rbp),%rdx
30: 48 83 c2 08 add $0x8,%rdx
34: 89 c6 mov %eax,%esi
36: 48 89 d7 mov %rdx,%rdi
39: e8 00 00 00 00 callq 3e <arcbase<true>::arcbase(int, int)+0x3e>
3e: c9 leaveq
3f: c3 retq
Whereas the second case:
Disassembly of section .text._ZN7arcbaseILb1EEC2Eii:
0000000000000000 <arcbase<true>::arcbase(int, int)>:
srd isrd;
arc iarc;
public:
arcbase(int a , int b) : isrd(a,b) , iarc( I == true ? isrd.nodes.head_in : isrd.nodes.head_out ) {}
0: 53 push %rbx
1: 48 89 fb mov %rdi,%rbx
4: e8 00 00 00 00 callq 9 <arcbase<true>::arcbase(int, int)+0x9>
9: 48 8d 7b 08 lea 0x8(%rbx),%rdi
d: 8b 33 mov (%rbx),%esi
f: 5b pop %rbx
10: e9 00 00 00 00 jmpq 15 <arcbase<true>::arcbase(int, int)+0x15>
Looking at the dissasembly you should notice that even in the first case the value of 10 is not directly passed as is to the constructor, but instead only placed in the register from where is is retrieved.
Here is the output from gdb :
0x400910 <_ZN3arcC2Ei> mov %esi,(%rdi)
0x400912 <_ZN3arcC2Ei+2> retq
0x400913 nop
0x400914 nop
0x400915 nop
0x400916 nop
0x400917 nop
0x400918 nop
0x400919 nop
0x40091a nop
0x40091b nop
0x40091c nop
0x40091d nop
0x40091e nop
0x40091f nop
0x400920 <_ZN7arcbaseILb1EEC2Eii> push %rbx
0x400921 <_ZN7arcbaseILb1EEC2Eii+1> mov %rdi,%rbx
0x400924 <_ZN7arcbaseILb1EEC2Eii+4> callq 0x400900 <_ZN3srdC2Eii>
0x400929 <_ZN7arcbaseILb1EEC2Eii+9> lea 0x8(%rbx),%rdi
0x40092d <_ZN7arcbaseILb1EEC2Eii+13> mov (%rbx),%esi
0x40092f <_ZN7arcbaseILb1EEC2Eii+15> pop %rbx
0x400930 <_ZN7arcbaseILb1EEC2Eii+16> jmpq 0x400910 <_ZN3arcC2Ei>
The code for the second case is :
struct llist
{
int head_in;
int head_out;
llist(int a , int b ) : head_in(a), head_out(b) {}
};
struct srd
{
llist nodes;
srd(int a, int b) : nodes(a,b) {}
};
struct arc
{
int y;
arc( int x):y(x) {}
};
template< bool I > class arcbase
{
srd isrd;
arc iarc;
public:
arcbase(int a , int b) : isrd(a,b) , iarc( I == true ? isrd.nodes.head_in : isrd.nodes.head_out ) {}
void print()
{
std::cout << iarc.y << std::endl;
}
};
int main(void)
{
std::srand(time(0));
volatile int a_ = std::rand()%100;
volatile int b_ = std::rand()%4;
arcbase<true> a(a_,b_);
a.print();
return 0;
}

What actually happens in C++ with function parameters?

I have been learning C++ for the past couple of months. I know with functions your first declare parameters like so:
int myFunc(int funcVar);
and then you can pass in an integer variable to that function like so:
int x = 5;
myFunc(x);
When passing an argument to a function I would usually think of it like assigning and copying the value of x into the parameter of myFunc, which in C++ would look like this:
funcVar = x;
However, I noticed when declaring functions which have parameters of references (or pointers):
int myFunc(int & funcVar);
that I can either pass in the variable x to myFunc:
myFunc(x);
which would look like (in my mind):
&funcVar = x;
or you can pass in an actual reference as the argument
int & rX = x;
myFunc(rX);
and the function would work as well which with my thinking would look like this statement in C++
int & funcVar = rX
which would not make sense assigning a reference to a reference. My question is then how does the compiler actually load in arguments in a function? Should I not think of it like assigning the value of the variable to the parameter of the function?
When you call a function, each parameter of the function is initialized (not assigned). The rules for this are the same as the rules for any other copy-initialization. So if you have
int myFunc(int funcVar);
int x = 5;
myFunc(x);
then funcVar is initialized as though by a statement like this:
int funcVar = x;
and if you have
int myFunc(int & funcVar);
myFunc(x);
int & rX = x;
myFunc(rX);
then funcVar is initialized (and not assigned) as though by statements like this:
int & funcVar = x;
int & funcVar = rX;
The initialization of a reference binds it to the object or function denoted by the initializer. The second initialization does make sense---the expression rX denotes the object x because rX is a reference bound to x. Therefore, initializing a reference with rX has the same effect as initializing a reference with x.
Let us make easy code and disassemble.
int by_value(int x) { return x; }
int by_reference(int &x) { return x; }
int by_pointer(int *x) { return *x; }
int main()
{
int x = 1;
by_value(x);
by_reference(x);
by_pointer(&x);
return 0;
}
$ g++ -g -O0 a.cpp ; objdump -dS a.out
In my environment (x86_64, g++ (SUSE Linux) 4.8.3 20140627), result is as following.
(full text is here http://ideone.com/Z5G8yz)
00000000004005dd <_Z8by_valuei>:
int by_value(int x) { return x; }
4005dd: 55 push %rbp
4005de: 48 89 e5 mov %rsp,%rbp
4005e1: 89 7d fc mov %edi,-0x4(%rbp)
4005e4: 8b 45 fc mov -0x4(%rbp),%eax
4005e7: 5d pop %rbp
4005e8: c3 retq
00000000004005e9 <_Z12by_referenceRi>:
int by_reference(int &x) { return x; }
4005e9: 55 push %rbp
4005ea: 48 89 e5 mov %rsp,%rbp
4005ed: 48 89 7d f8 mov %rdi,-0x8(%rbp)
4005f1: 48 8b 45 f8 mov -0x8(%rbp),%rax
4005f5: 8b 00 mov (%rax),%eax
4005f7: 5d pop %rbp
4005f8: c3 retq
00000000004005f9 <_Z10by_pointerPi>:
int by_pointer(int *x) { return *x; }
4005f9: 55 push %rbp
4005fa: 48 89 e5 mov %rsp,%rbp
4005fd: 48 89 7d f8 mov %rdi,-0x8(%rbp)
400601: 48 8b 45 f8 mov -0x8(%rbp),%rax
400605: 8b 00 mov (%rax),%eax
400607: 5d pop %rbp
400608: c3 retq
0000000000400609 <main>:
int main()
{
400609: 55 push %rbp
40060a: 48 89 e5 mov %rsp,%rbp
40060d: 48 83 ec 10 sub $0x10,%rsp
int x = 1;
400611: c7 45 fc 01 00 00 00 movl $0x1,-0x4(%rbp)
by_value(x);
400618: 8b 45 fc mov -0x4(%rbp),%eax
40061b: 89 c7 mov %eax,%edi
40061d: e8 bb ff ff ff callq 4005dd <_Z8by_valuei>
by_reference(x);
400622: 48 8d 45 fc lea -0x4(%rbp),%rax
400626: 48 89 c7 mov %rax,%rdi
400629: e8 bb ff ff ff callq 4005e9 <_Z12by_referenceRi>
by_pointer(&x);
40062e: 48 8d 45 fc lea -0x4(%rbp),%rax
400632: 48 89 c7 mov %rax,%rdi
400635: e8 bf ff ff ff callq 4005f9 <_Z10by_pointerPi>
return 0;
40063a: b8 00 00 00 00 mov $0x0,%eax
}
by_reference(x) is as same as by_pointer(&x) !
It makes perfect sense to assign a reference to another reference (when first defining it, i.e. at initialization), and that's what actually happens. A reference is just an alias, so when you assign a reference to another reference you are just saying that the first one aliases the one you assigned. Example
int x = 42;
int& rx = x;
int& ry = rx;
++ry;
std::cout << x; // displays 43
Live on Coliru

Is it possible to take the address of an ADL function?

Is it possible to take the address of a function that would be found through ADL?
For example:
template<class T>
void (*get_swap())(T &, T &)
{
return & _________; // how do I take the address of T's swap() function?
}
int main()
{
typedef some_type T;
get_swap<T>();
}
Honestly, I don't know but I tend towards saying that this is not possible.
Depending on what you want to achieve I can suggest a workaround. More precisely, if you just need the address of a function that has the same semantics as swap called through ADL then you can use this:
template <typename T>
void (*get_swap())(T&, T&) {
return [](T& x, T& y) { return swap(x, y); };
}
For instance, the following code:
namespace a {
struct b {
int i;
};
void swap(b& x, b& y) {
std::swap(x.i, y.i);
}
}
int main() {
auto f0 = (void (*)(a::b&, a::b&)) a::swap;
auto f1 = get_swap<a::b>();
std::cout << std::hex;
std::cout << (unsigned long long) f0 << '\n';
std::cout << (unsigned long long) f1 << '\n';
}
compiled with gcc 4.8.1 (-std=c++11 -O3) on my machine gave:
4008a0
4008b0
The relevant assembly code (objdump -dSC a.out) is
00000000004008a0 <a::swap(a::b&, a::b&)>:
4008a0: 8b 07 mov (%rdi),%eax
4008a2: 8b 16 mov (%rsi),%edx
4008a4: 89 17 mov %edx,(%rdi)
4008a6: 89 06 mov %eax,(%rsi)
4008a8: c3 retq
4008a9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
00000000004008b0 <void (*get_swap<a::b>())(a::b&, a::b&)::{lambda(a::b&, a::b&)#1}::_FUN(a::b&, a::b&)>:
4008b0: 8b 07 mov (%rdi),%eax
4008b2: 8b 16 mov (%rsi),%edx
4008b4: 89 17 mov %edx,(%rdi)
4008b6: 89 06 mov %eax,(%rsi)
4008b8: c3 retq
4008b9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
As one can see the functions pointed by f0 and f1 (located at 0x4008a0 and 0x4008b0, respectively) are binary identical. The same holds when compiled with clang 3.3.
If the linker can do identical COMDAT folding (ICF), I guess, we can even get f0 == f1. (For more on ICF see this post.)

why boost::scoped_ptr is cleared in a singleton implementation

I implemented a simple singleton by using boost::scoped_ptr:
template <class T> class Singleton : public boost::noncopyable {
public:
static T& instance() {
boost::call_once(init, flag);
return *t;
}
static void init() {
t.reset(new T());
}
private:
static boost::scoped_ptr <T> t;
static boost::once_flag flag;
};
template <class T> boost::scoped_ptr<T> Singleton<T>::t(0);
template <class T> boost::once_flag Singleton<T>::flag = BOOST_ONCE_INIT;
Define a real singleton class:
class S : public Singleton<S> {
public:
void p() { printf("hello");}
};
Then I define a static variable in file S.cpp:
static volatile S &S_instance = S::instance();
in main.cpp:
int main()
{
S &ss = S::instance();
ss.p();
}
Run this program, an exception happened:
/usr/include/boost/smart_ptr/scoped_ptr.hpp:91: T& boost::scoped_ptr::operator*() const [with T = S]: Assertion `px != 0' failed
Tracing the code, I found the static s_instance.t is cleared once the code leaves the static initialization segment and after that all code referring to the S::instance will get NULL scoped_ptr. Does anybody know the reason?
[UPDATE]
I tried to put all static into one cpp file (S1.cpp):
template <class T> boost::scoped_ptr<T> Singleton<T>::t(0);
template <class T> boost::once_flag Singleton<T>::flag = BOOST_ONCE_INIT;
static S& s_ins = S::instance();
and debug it with GDB, it looks follow the order I wrote. any idea?
A possible reason is that the static template <class T> boost::scoped_ptr<T> Singleton<T>::t(0); is initialized after the static volatile S &S_instance = S::instance();, and thus it replaced with 0 the value previously stored in t. The order in which the static variables are constructed is only defined within a single compilation unit, and I guess in your case t can be instantiated inside main.cpp (or rather in both files at compilation time, and the linker will have to choose just one) while S resides in S.cpp. Just a guess though.
I'm pretty sure that it is undefined behavior due to undefined creation order of global variables. So, your S_instance first initialized, and then template <class T> boost::scoped_ptr<T> Singleton<T>::t(0)
Such a simple program can illustrate, what can happen when order is reversed:
#include <iostream>
std::string &getS();
std::string& t = getS();
std::string s("hello");
std::string &getS() {s = "world"; return s;}
int main()
{
std::cout << t;
}
Well, it crashes for me with g++ and prints hello with cl
Your program works correctly (when compiled as a single file) if you remove this line:
static volatile S &S_instance = S::instance();
OK. When built on my machine without your S_instance declaration:
0000000000400d86 <__static_initialization_and_destruction_0(int, int)>:
400d86: 55 push %rbp
400d87: 48 89 e5 mov %rsp,%rbp
400d8a: 48 83 ec 10 sub $0x10,%rsp
400d8e: 89 7d fc mov %edi,-0x4(%rbp)
400d91: 89 75 f8 mov %esi,-0x8(%rbp)
400d94: 83 7d fc 01 cmpl $0x1,-0x4(%rbp)
400d98: 75 43 jne 400ddd <__static_initialization_and_destruction_0(int, int)+0x57>
400d9a: 81 7d f8 ff ff 00 00 cmpl $0xffff,-0x8(%rbp)
400da1: 75 3a jne 400ddd <__static_initialization_and_destruction_0(int, int)+0x57>
400da3: b8 b8 40 40 00 mov $0x4040b8,%eax
400da8: 0f b6 00 movzbl (%rax),%eax
400dab: 84 c0 test %al,%al
400dad: 75 2e jne 400ddd <__static_initialization_and_destruction_0(int, int)+0x57>
400daf: b8 b8 40 40 00 mov $0x4040b8,%eax
400db4: c6 00 01 movb $0x1,(%rax)
400db7: be 00 00 00 00 mov $0x0,%esi
400dbc: bf b0 40 40 00 mov $0x4040b0,%edi
400dc1: e8 3c 05 00 00 callq 401302 <boost::scoped_ptr<S>::scoped_ptr(S*)>
400dc6: b8 da 13 40 00 mov $0x4013da,%eax
400dcb: ba 90 40 40 00 mov $0x404090,%edx
400dd0: be b0 40 40 00 mov $0x4040b0,%esi
400dd5: 48 89 c7 mov %rax,%rdi
400dd8: e8 8b fd ff ff callq 400b68 <__cxa_atexit#plt>
400ddd: c9 leaveq
400dde: c3 retq
When compiled with your S_instance declaration:
0000000000400d86 <__static_initialization_and_destruction_0(int, int)>:
400d86: 55 push %rbp
400d87: 48 89 e5 mov %rsp,%rbp
400d8a: 48 83 ec 10 sub $0x10,%rsp
400d8e: 89 7d fc mov %edi,-0x4(%rbp)
400d91: 89 75 f8 mov %esi,-0x8(%rbp)
400d94: 83 7d fc 01 cmpl $0x1,-0x4(%rbp)
400d98: 75 4f jne 400de9 <__static_initialization_and_destruction_0(int, int)+0x63>
400d9a: 81 7d f8 ff ff 00 00 cmpl $0xffff,-0x8(%rbp)
400da1: 75 46 jne 400de9 <__static_initialization_and_destruction_0(int, int)+0x63>
400da3: e8 c2 04 00 00 callq 40126a <Singleton<S>::instance()>
400da8: 48 89 05 01 33 00 00 mov %rax,0x3301(%rip) # 4040b0 <S_instance>
400daf: b8 c0 40 40 00 mov $0x4040c0,%eax
400db4: 0f b6 00 movzbl (%rax),%eax
400db7: 84 c0 test %al,%al
400db9: 75 2e jne 400de9 <__static_initialization_and_destruction_0(int, int)+0x63>
400dbb: b8 c0 40 40 00 mov $0x4040c0,%eax
400dc0: c6 00 01 movb $0x1,(%rax)
400dc3: be 00 00 00 00 mov $0x0,%esi
400dc8: bf b8 40 40 00 mov $0x4040b8,%edi
400dcd: e8 3c 05 00 00 callq 40130e <boost::scoped_ptr<S>::scoped_ptr(S*)>
400dd2: b8 e6 13 40 00 mov $0x4013e6,%eax
400dd7: ba 90 40 40 00 mov $0x404090,%edx
400ddc: be b8 40 40 00 mov $0x4040b8,%esi
400de1: 48 89 c7 mov %rax,%rdi
400de4: e8 7f fd ff ff callq 400b68 <__cxa_atexit#plt>
400de9: c9 leaveq
400dea: c3 retq
In the latter code, you can clearly see that the constructor for the static scoped_ptr happens after S_instance.
The above were compiled from:
#include <cstdio>
#include <boost/scoped_ptr.hpp>
#include <boost/thread/once.hpp>
#include <boost/noncopyable.hpp>
template <class T> class Singleton : public boost::noncopyable {
public:
static T& instance() {
boost::call_once(init, flag);
return *t;
}
static void init() {
t.reset(new T());
}
private:
static boost::scoped_ptr <T> t;
static boost::once_flag flag;
};
template <class T> boost::scoped_ptr<T> Singleton<T>::t(0);
template <class T> boost::once_flag Singleton<T>::flag = BOOST_ONCE_INIT;
class S : public Singleton<S> {
public:
void p() { printf("hello");}
};
// static volatile S &S_instance = S::instance();
int main()
{
S &ss = S::instance();
ss.p();
}
I think that this is wrong:
static volatile S &S_instance = S::instance();
because it creates and then deletes the instance. You want a pointer, not a reference.
static S *S_instance = &S::instance();
As far as I know the reference goes out of scope at the end of the .cpp file. The instance is not expected to ever be deleted anyway, so T can just be a bare pointer.