is a bad idea.
Production code
$ cat base.cpp
#include "base.h"
std::shared_ptr<Base> new_base()
{
auto out = std::make_shared<Base>();
return out;
}
$ cat base.h
#include <memory>
#include <stdio.h>
class Base {
public:
virtual void do_something() { printf("base class\n"); }
};
Mock and test harness.
$ cat mock.h
class Mock: public Base {
public:
void do_something() override { printf("mock class\n"); }
};
// XXX template specialization trick.
namespace std {
template <>
shared_ptr<Base>
make_shared<Base>() {
auto out = make_shared<Mock>();
return std::dynamic_pointer_cast<Base>(out);
}
};
$ cat test.cpp
#include "base.h"
#include "mock.h"
extern std::shared_ptr<Base> new_base();
int main()
{
auto out = new_base();
out->do_something();
}
Works fine in non-optimized build.
$ g++ -Wall -g -std=c++1z -O0 base.cpp test.cpp
$ ./a.out
mock class
But fails in O3 build.
$ g++ -Wall -g -std=c++1z -O3 base.cpp test.cpp
$ ./a.out
base class
つまり、std::make_shared を、テンプレート特殊化で乗っ取って、製品クラスでのクラス生成の戻り値にモックオブジェクトを返そうとした。
O0 ビルドしたら動いたけど、 O3 ビルドしたら、動かなかった。
こういうことはしてはいけない。という話。
要件と説明
製品コードには一切、手を入れない。テストハーネスで、モックのヘッダを引く。make_shared<Base> が、実体化する。製品コードでの呼び出しは、それをリンクするため、モックが確保される。
最適化ビルドすると、make_shared<Base> が、実体化しないので、テンプレート特殊が効かない。
引いてないヘッダに存在するテンプレートは、 cpp ファイルから使えるわけ無いでしょ。ということ。
$ g++ -Wall -g -std=c++1z -O0 -c base.cpp
$ g++ -Wall -g -std=c++1z -O0 -c test.cpp
$ nm --demangle base.o | grep std::make_shared
00000000 W std::shared_ptr<Base> std::make_shared<Base>()
$ nm --demangle test.o | grep std::make_shared
000000d5 T std::shared_ptr<Base> std::make_shared<Base>()
00000000 W std::shared_ptr<Mock> std::make_shared<Mock>()
$ g++ -Wall -g -std=c++1z -O3 -c base.cpp
$ g++ -Wall -g -std=c++1z -O3 -c test.cpp
$ nm --demangle base.o | grep std::make_shared
$ nm --demangle test.o | grep std::make_shared
00000000 T std::shared_ptr<Base> std::make_shared<Base>()
$ gdb a.out
(gdb) b std::make_shared<Base>
Breakpoint 2 at 0x8048788: std::make_shared<Base>. (2 locations)
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x08048610 in main() at test.cpp:5
breakpoint already hit 1 time
2 breakpoint keep y <MULTIPLE>
2.1 y 0x08048788 in new_base() at /usr/include/c++/6.4.1/bits/shared_ptr_base.h:1100
2.2 y 0x08048860 in std::make_shared<Base>() at mock.h:9
(gdb) run
Breakpoint 2, new_base () at base.cpp:5
5 auto out = std::make_shared<Base>();
(gdb) s
std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count<Base, std::allocator<Base>>(std::_Sp_make_shared_tag, Base*, std::allocator<Base> const&) (__a=..., this=0xbffff14c) at /usr/include/c++/6.4.1/bits/shared_ptr_base.h:609
609 : _M_pi(0)
(gdb) bt
#0 std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count<Base, std::allocator<Base>>(std::_Sp_make_shared_tag, Base*, std::allocator<Base> const&) (__a=..., this=0xbffff14c) at /usr/include/c++/6.4.1/bits/shared_ptr_base.h:609
#1 std::__shared_ptr<Base, (__gnu_cxx::_Lock_policy)2>::__shared_ptr<std::allocator<Base>>(std::_Sp_make_shared_tag, std::allocator<Base> const&) (__a=..., __tag=..., this=0xbffff148) at /usr/include/c++/6.4.1/bits/shared_ptr_base.h:1100
#2 std::shared_ptr<Base>::shared_ptr<std::allocator<Base>>(std::_Sp_make_shared_tag, std::allocator<Base> const&) (
__a=..., __tag=..., this=0xbffff148) at /usr/include/c++/6.4.1/bits/shared_ptr.h:319
#3 std::allocate_shared<Base, std::allocator<Base>>(std::allocator<Base> const&) (__a=...)
at /usr/include/c++/6.4.1/bits/shared_ptr.h:620
#4 std::make_shared<Base> () at /usr/include/c++/6.4.1/bits/shared_ptr.h:636
#5 new_base () at base.cpp:5
#6 0x0804862b in main () at test.cpp:6
(gdb) c
Continuing.
base class
[Inferior 1 (process 2577) exited normally]
$ g++ --version
g++ (GCC) 6.4.1 20170727 (Red Hat 6.4.1-1)
これでわかるように、最適化ビルドすると、 製品コードでの make_shared<Base> は、shared_ptr.h にあるものが使われ、テストハーネスで実体化した関数は使われない。
製品コードで、 mock.h を引けば、当然、テンプレート特殊化が効くが、 ifdef が必要になってしまうので、それを嫌ったので、はまることになった。
以上