[英]Deleting templated class pointers in a non-templated class destructor?
The below code test-templated-destructor.cpp
replicates the organization of a library I'm using. 下面的代码test-templated-destructor.cpp
复制了我正在使用的库的组织。 I'm using: 我正在使用:
$ cat /etc/issue
Ubuntu 14.04.5 LTS \n \l
$ g++ --version
g++ (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4
$ g++ -std=c++14
g++: error: unrecognized command line option ‘-std=c++14’
g++: fatal error: no input files
compilation terminated.
$ g++ -std=c++11
g++: fatal error: no input files
compilation terminated.
There is: 有:
AA
, and classes derived from it BB
and CC
; 基类AA
以及从中衍生的类BB
和CC
; AAInstancer
, and class derived from it AAInstancerTemplated
which is templated 抽象类AAInstancer
和类从它派生AAInstancerTemplated
这是模板 AAHandler
, which has a templated function addTemplatedObject
, which stores AAInstancer*
pointers to new AAInstancerTemplated<T>()
objects, in a map
property of the class 类AAHandler
,具有模板化函数addTemplatedObject
,该函数在类的map
属性中存储指向new AAInstancerTemplated<T>()
对象的AAInstancer*
指针 main()
, an AAHandler
object is instantiated, and .addTemplatedObject<BB>("BB");
在main()
,实例化AAHandler
对象,然后添加.addTemplatedObject<BB>("BB");
called on it 呼吁 If I run valgrind
on this, it reports: 如果我对此运行valgrind
,则会报告:
==21000== 43 (16 direct, 27 indirect) bytes in 1 blocks are definitely lost in loss record 2 of 2
==21000== at 0x4C2B0E0: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==21000== by 0x40141B: void AAHandler::addTemplatedObject<BB>(std::string) (test-templated-destructor.cpp:64)
==21000== by 0x40113E: main (test-templated-destructor.cpp:82)
I think the problem is that we used new
in addTemplatedObject()
, thus we should correspondingly delete it latest at program exit - but that is not done, and thus the reason for the leak. 我认为问题在于我们在addTemplatedObject()
使用了new
,因此我们应该在程序退出时相应地将其最新删除-但这没有完成,因此是泄漏的原因。
So I thought, to write an iterator that loops through the instancers
map, and delete
s these pointers in the desctructor of the AAHandler
, but I can't: 因此,我想编写一个遍历instancers
映射的迭代器,并在AAHandler的AAHandler
器中delete
这些指针,但是我不能:
~AAHandler() {
cout << " (running AAHandler destructor)" << endl;
map<string, AAInstancer*>::iterator it;
for ( it = instancers.begin(); it != instancers.end(); it++ ) {
delete it->second;
}
}
... then I get at compilation: ...然后我进行编译:
$ g++ -g -Wall test-templated-destructor.cpp -o test-templated-destructor.exe
test-templated-destructor.cpp: In destructor ‘AAHandler::~AAHandler()’:
test-templated-destructor.cpp:60:18: warning: deleting object of abstract class type ‘AAInstancer’ which has non-virtual destructor will cause undefined behaviour [-Wdelete-non-virtual-dtor]
delete it->second;
^
... and it sounds right - AAInstancer
doesn't have a destructor defined, so compiler probably auto-added is as non-virtual, leading to this warning (although, running this through valgrind
will show that leaks are not detected anymore). ...听起来AAInstancer
没有定义析构函数,因此编译器可能自动添加为非虚拟的,从而导致此警告(尽管通过valgrind
运行此命令将显示不再检测到泄漏)。
template <class T>
~AAHandler() {
cout << " (running AAHandler destructor)" << endl;
map<string, AAInstancer*>::iterator it;
for ( it = instancers.begin(); it != instancers.end(); it++ ) {
delete (AAInstancerTemplated<T>*)it->second;
}
}
... in hope that this destructor would get called if we called addTemplatedObject
with some template (which it wouldn't anyway), compilation fails: ...希望如果我们使用一些模板调用addTemplatedObject
时会调用此析构函数(无论如何都不会),编译会失败:
$ g++ -g -Wall test-templated-destructor.cpp -o test-templated-destructor.exe && ./test-templated-destructor.exe
test-templated-destructor.cpp:57:14: error: destructor ‘AAHandler::~AAHandler()’ declared as member template
~AAHandler() {
^
... and this makes sense too: AAHandler
is a non-templated class, so probably its destructor shouldn't be templated either. ……这也很有意义: AAHandler
是一个非模板类,因此它的析构函数也不应被模板化。
So, is it possible to write a destructor for AAHandler
, which would delete
all new
pointers in its instancers
, regardless of which template they were instantiated with - with minimal (or best, no) changes to the already existing code? 那么,是否有可能为AAHandler
编写一个析构AAHandler
,该析构函数将delete
其instancers
所有new
指针,而不管它们使用哪个模板实例化-只需对现有代码进行最少(或最好,无)更改?
test-templated-destructor.cpp
// g++ -g -Wall test-templated-destructor.cpp -o test-templated-destructor.exe && ./test-templated-destructor.exe
// valgrind --leak-check=yes ./test-templated-destructor.exe
#include <iostream>
#include <map>
using namespace std;
class AA {
public:
string myname;
AA() {
myname = "";
cout << " AA instantiated\n";
}
};
class BB : public AA {
public:
string mystuff;
BB() {
mystuff = "";
cout << " BB instantiated\n";
}
};
class CC : public AA {
public:
string mythings;
CC() {
mythings = "";
cout << " CC instantiated\n";
}
};
class AAInstancer
{
public:
virtual AA* createInstance() = 0;
string tagName;
};
template <class T>
class AAInstancerTemplated: public AAInstancer
{
public:
AA* createInstance() {
return new T();
}
};
class AAHandler
{
public:
~AAHandler() { }
AAHandler() { }
static map<string, AAInstancer*> instancers;
template <class T>
static void addTemplatedObject(string tagName) {
AAInstancer* instancer = new AAInstancerTemplated<T>();
instancer->tagName = tagName;
instancers[tagName] = instancer;
}
AAHandler* get() {
if(singleton == NULL)
singleton = new AAHandler();
return singleton;
}
private:
static AAHandler* singleton;
};
map<string, AAInstancer*> AAHandler::instancers;
int main()
{
AAHandler aah;
aah.addTemplatedObject<BB>("BB");
cout << "Address of aah: " << static_cast<void*>(&aah) << endl;
return 0;
}
AAInstancer
needs a virtual destructor. AAInstancer
需要一个虚拟析构函数。 If it doesn't need a body you can default it. 如果不需要主体,则可以默认。
virtual ~AAInstancer() = default;
Use a std::unique_ptr<AAInstancer>
: 使用std::unique_ptr<AAInstancer>
:
map<string, std::unique_ptr<AAInstancer>>
as member, instead of managing the memory yourself. 作为成员,而不是自己管理内存。
OK, finally got something to work which compiles fine under just c++11
and does not leak; 好的,终于有了可以在c++11
下编译良好且不会泄漏的东西。 valgrind
reports: valgrind
报告:
$ valgrind --leak-check=yes ./test-templated-destructor.exe==22888== Memcheck, a memory error detector
==22888== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==22888== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==22888== Command: ./test-templated-destructor.exe
==22888==
Address of aah: 0xffefffb3f
(running AAHandler destructor)
~AAInstancerTemplated <2BB> here; tref: 0x5a200b0
~AAInstancer here
~AAInstancerTemplated <2CC> here; tref: 0x5a201e0
~AAInstancer here
==22888==
==22888== HEAP SUMMARY:
==22888== in use at exit: 0 bytes in 0 blocks
==22888== total heap usage: 6 allocs, 6 frees, 198 bytes allocated
==22888==
==22888== All heap blocks were freed -- no leaks are possible
==22888==
==22888== For counts of detected and suppressed errors, rerun with: -v
==22888== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Yup, no leaks - I like that :)
是的,没有泄漏-我喜欢:)
The approach is a bit classic: make AAInstancerTemplated
keep a reference (here tref
) to whatever it is instantiating via new
, then create a destructor for it (for AAInstancerTemplated
) that delete
s this reference. 该方法有点经典:使AAInstancerTemplated
保留对通过new
实例化的AAInstancerTemplated
的引用(此处为tref
),然后为其创建析构函数(针对AAInstancerTemplated
),以delete
该引用。
Note that even if in AAHandler
, we store generic pointers AAInstancer*
in instancers
, while we instantiate templated objects ( new AAInstancerTemplated<T>();
) -- now, with this organisation, when we delete it->second
which is of type AAInstancer*
, the right templated destructor gets called. 请注意,即使在AAHandler
,我们AAInstancer*
在instancers
化器中存储通用指针AAInstancer*
,而我们实例化模板化对象( new AAInstancerTemplated<T>();
)时–现在,通过此组织,当我们delete it->second
类型AAInstancer*
,将调用正确的模板化析构函数。
The fixed test-templated-destructor.cpp
: 固定的test-templated-destructor.cpp
:
// g++ -g -std=c++11 test-templated-destructor.cpp -o test-templated-destructor.exe && ./test-templated-destructor.exe
// valgrind --leak-check=yes ./test-templated-destructor.exe
#include <iostream>
#include <map>
#include <typeinfo>
#include <functional> // note: uses std::function, which is c++11 feature
using namespace std;
class AA {
public:
string myname;
AA() {
myname = "";
cout << " AA instantiated\n";
}
};
class BB : public AA {
public:
string mystuff;
BB() {
mystuff = "";
cout << " BB instantiated\n";
}
};
class CC : public AA {
public:
string mythings;
CC() {
mythings = "";
cout << " CC instantiated\n";
}
};
class AAInstancer
{
public:
virtual ~AAInstancer() {
cout << " ~AAInstancer here" << endl;
}
virtual AA* createInstance() = 0;
string tagName;
};
template <class T>
class AAInstancerTemplated: public AAInstancer
{
public:
T* tref;
AA* createInstance() {
if (tref) delete tref;
tref = new T();
return tref;
}
~AAInstancerTemplated() {
cout << " ~AAInstancerTemplated <" << typeid(T).name() << "> here; tref: " << static_cast<void*>(&tref) << endl;
if (tref) delete tref;
}
};
class AAHandler
{
public:
~AAHandler() {
cout << " (running AAHandler destructor)" << endl;
typedef typename map<string, AAInstancer*>::iterator instIterator;
for ( instIterator it = instancers.begin(); it != instancers.end(); it++ ) {
delete it->second;
}
}
AAHandler() { }
static map<string, AAInstancer*> instancers;
template <class T>
static void addTemplatedObject(string tagName) {
AAInstancer* instancer = new AAInstancerTemplated<T>();
instancer->tagName = tagName;
instancers[tagName] = instancer;
}
AAHandler* get() {
if(singleton == NULL)
singleton = new AAHandler();
return singleton;
}
private:
static AAHandler* singleton;
};
map<string, AAInstancer*> AAHandler::instancers;
int main()
{
AAHandler aah;
aah.addTemplatedObject<BB>("BB");
aah.addTemplatedObject<CC>("CC");
cout << "Address of aah: " << static_cast<void*>(&aah) << endl;
return 0;
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.