不调用 C++ 析构函数
C++ Destructor is not called
我有三个类:房间、门和世界
#include <set>;
using namespace std;
class Door; // Forward declaration
class Room {
public:
Door* door1;
Door* door2;
Room(){}
~Room() {
delete door1;
door1 = 0;
delete door2;
door2 = 0;
}
};
class Door {
public:
Room* roomA;
Room* roomB;
Door(Room* roomA, Room* roomB) {
this->roomA = roomA;
this->roomB = roomB;
linkRooms(); // This sets up the Door-Pointers in the Rooms
// so they know about the new door.
}
~Door() {
// Returns the room-owned pointer pointing at this door
getMyRoomPointer(roomA) = 0;
getMyRoomPointer(roomB) = 0;
}
Door * & getMyRoomPointer(Room * const & room) {
if (room->door1 == this) return room->door1;
else return room->door2;
}
void linkRooms() {
roomA->door1 = this;
roomB->door2 = this;
}
};
class World {
public:
std::set<Room*> rooms;
World() {
// Set up two rooms and link them using a door
Room* newRoom = new Room();
rooms.insert(newRoom);
Room* anotherNewRoom = new Room();
rooms.insert(anotherNewRoom);
new Door(newRoom, anotherNewRoom);
}
~World() {
// Iterate over the rooms and call delete on all of them
for (std::set<Room*>::iterator it = rooms.begin(); it != rooms.end(); ++it) {
delete *it;
}
}
};
int main() {
World world;
return 0;
}
当 运行 主要时,构造函数只用两个房间和一扇门填充世界,作为它们之间的 link。 main returns 之后,world 应该被删除,所有的房间和门也应该被处理。
问题是,我的 Door 析构函数从未被调用过。所以房间内的门指针没有设置为空,当房间 "at the other side" 试图删除同一扇门时我得到一个错误。
当我刚创建一个 Door 实例,然后立即删除它时,我没有遇到任何问题:
int main(){
Room oneRoom;
Room anotherRoom;
Door* door = new Door(&oneRoom, &anotherRoom);
delete door; // works just fine
return 0;
}
问题:为什么不调用 Door 构造函数?是否允许像第一个示例中那样设置门?
我知道,我正在双重删除我房间的门指针,并且我可以(并且应该)使用 SmartPointers。现在我只是想知道为什么我要面对这种行为。毕竟,我还是 C++ 的新手。
我现在确实设置了一个可运行的示例,它重现了错误。
您在定义 Door
之前调用了 delete
。因此程序的行为是未定义的,不能保证调用析构函数。
引自标准(草案)[expr.delete]:
- If the object being deleted has incomplete class type at the point of deletion and the complete class has a non-trivial destructor or a deallocation function, the behavior is undefined.
解决方案:如果类型的析构函数是非平凡的(即用户定义的,例如 ~Door
,则在类型完成之前永远不要删除此类对象)。在这种情况下,在调用 delete 的函数之前定义 Door
。
作为一般规则,除非 class 类型完整,否则永远不能调用成员函数。不幸的是,对于析构函数,编译器可能并不总是能够捕获错误。 PS。 g++ 会警告您的程序:warning: possible problem detected in invocation of delete operator: [-Wdelete-incomplete]
你永远不会删除门
new Door(newRoom, anotherNewRoom);
你应该在世界
中添加Door *door
public:
Door *door;
...
door = new Door(newRoom, anotherNewRoom);
并在world的析构函数中调用delete:
delete door;
请注意,您的代码有很多未定义的行为。
std::set<Room *> rooms;
std::set
是一个关联容器,其中包含一组已排序的键类型的唯一对象。
你应该使用列表:
std::list<Room *> rooms;
您应该注意编译器警告消息:
g++ -std=c++17 -fPIC -g -Wall -Wextra -Wwrite-strings -Wno-parentheses -Wpedantic -Warray-bounds -Weffc++ 41120443.cpp -o 41120443
41120443.cpp:1:15: warning: extra tokens at end of #include directive
#include <set>;
^
41120443.cpp:7:7: warning: ‘class Room’ has pointer data members [-Weffc++]
class Room {
^~~~
41120443.cpp:7:7: warning: but does not override ‘Room(const Room&)’ [-Weffc++]
41120443.cpp:7:7: warning: or ‘operator=(const Room&)’ [-Weffc++]
41120443.cpp: In constructor ‘Room::Room()’:
41120443.cpp:12:5: warning: ‘Room::door1’ should be initialized in the member initialization list [-Weffc++]
Room(){}
^~~~
41120443.cpp:12:5: warning: ‘Room::door2’ should be initialized in the member initialization list [-Weffc++]
41120443.cpp: In destructor ‘Room::~Room()’:
41120443.cpp:15:16: warning: possible problem detected in invocation of delete operator: [-Wdelete-incomplete]
delete door1;
^~~~~
41120443.cpp:15:16: warning: invalid use of incomplete type ‘class Door’
41120443.cpp:5:7: note: forward declaration of ‘class Door’
class Door; // Forward declaration
^~~~
41120443.cpp:15:16: note: neither the destructor nor the class-specific operator delete will be called, even if they are declared when the class is defined
delete door1;
^~~~~
41120443.cpp:17:16: warning: possible problem detected in invocation of delete operator: [-Wdelete-incomplete]
delete door2;
^~~~~
41120443.cpp:17:16: warning: invalid use of incomplete type ‘class Door’
41120443.cpp:5:7: note: forward declaration of ‘class Door’
class Door; // Forward declaration
^~~~
41120443.cpp:17:16: note: neither the destructor nor the class-specific operator delete will be called, even if they are declared when the class is defined
delete door2;
^~~~~
41120443.cpp: At global scope:
41120443.cpp:22:7: warning: ‘class Door’ has pointer data members [-Weffc++]
class Door {
^~~~
41120443.cpp:22:7: warning: but does not override ‘Door(const Door&)’ [-Weffc++]
41120443.cpp:22:7: warning: or ‘operator=(const Door&)’ [-Weffc++]
41120443.cpp: In constructor ‘Door::Door(Room*, Room*)’:
41120443.cpp:27:5: warning: ‘Door::roomA’ should be initialized in the member initialization list [-Weffc++]
Door(Room* roomA, Room* roomB) {
^~~~
41120443.cpp:27:5: warning: ‘Door::roomB’ should be initialized in the member initialization list [-Weffc++]
41120443.cpp: In constructor ‘World::World()’:
41120443.cpp:56:5: warning: ‘World::rooms’ should be initialized in the member initialization list [-Weffc++]
World() {
^~~~~
和 Valgrind 输出:
valgrind ./41120443
==2864== Memcheck, a memory error detector
==2864== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==2864== Using Valgrind-3.12.0.SVN and LibVEX; rerun with -h for copyright info
==2864== Command: ./41120443
==2864==
==2864== Conditional jump or move depends on uninitialised value(s)
==2864== at 0x4C2C291: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==2864== by 0x108BC3: Room::~Room() (41120443.cpp:17)
==2864== by 0x108D67: World::~World() (41120443.cpp:68)
==2864== by 0x108B65: main (41120443.cpp:75)
==2864==
==2864== Conditional jump or move depends on uninitialised value(s)
==2864== at 0x4C2C291: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==2864== by 0x108BA8: Room::~Room() (41120443.cpp:15)
==2864== by 0x108D67: World::~World() (41120443.cpp:68)
==2864== by 0x108B65: main (41120443.cpp:75)
==2864==
==2864== Invalid free() / delete / delete[] / realloc()
==2864== at 0x4C2C2DB: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==2864== by 0x108BC3: Room::~Room() (41120443.cpp:17)
==2864== by 0x108D67: World::~World() (41120443.cpp:68)
==2864== by 0x108B65: main (41120443.cpp:75)
==2864== Address 0x5a82e00 is 0 bytes inside a block of size 16 free'd
==2864== at 0x4C2C2DB: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==2864== by 0x108BA8: Room::~Room() (41120443.cpp:15)
==2864== by 0x108D67: World::~World() (41120443.cpp:68)
==2864== by 0x108B65: main (41120443.cpp:75)
==2864== Block was alloc'd at
==2864== at 0x4C2B21F: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==2864== by 0x108CCE: World::World() (41120443.cpp:63)
==2864== by 0x108B54: main (41120443.cpp:75)
您可以通过明智地使用智能指针让自己的生活变得更轻松:
#include <memory>
#include <set>
class Door; // Forward declaration
struct Room {
std::shared_ptr<Door> door1 = {};
std::shared_ptr<Door> door2 = {};
~Room();
};
struct Door {
const std::weak_ptr<Room> roomA;
const std::weak_ptr<Room> roomB;
Door(std::shared_ptr<Room> roomA, std::shared_ptr<Room> roomB)
: roomA(roomA),
roomB(roomB)
{
roomA->door1 = roomB->door1 = std::shared_ptr<Door>{this};
}
~Door() = default;
};
// Now that Door is complete, we can define ~Room
Room::~Room() = default;
struct World {
std::set<std::shared_ptr<Room>> rooms = {};
World() {
// Set up two rooms and link them using a door
auto newRoom = std::make_shared<Room>();
rooms.insert(newRoom);
auto anotherNewRoom = std::make_shared<Room>();
rooms.insert(anotherNewRoom);
new Door(newRoom, anotherNewRoom);
}
~World() = default;
};
int main() {
World world;
return 0;
}
这构建并干净地运行:
g++ -std=c++17 -fPIC -g -Wall -Wextra -Wwrite-strings -Wno-parentheses -Wpedantic -Warray-bounds -Weffc++ 41120443.cpp -o 41120443
valgrind ./41120443
==3254== Memcheck, a memory error detector
==3254== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==3254== Using Valgrind-3.12.0.SVN and LibVEX; rerun with -h for copyright info
==3254== Command: ./41120443
==3254==
==3254==
==3254== HEAP SUMMARY:
==3254== in use at exit: 0 bytes in 0 blocks
==3254== total heap usage: 7 allocs, 7 frees, 72,952 bytes allocated
==3254==
==3254== All heap blocks were freed -- no leaks are possible
==3254==
==3254== For counts of detected and suppressed errors, rerun with: -v
==3254== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
我有三个类:房间、门和世界
#include <set>;
using namespace std;
class Door; // Forward declaration
class Room {
public:
Door* door1;
Door* door2;
Room(){}
~Room() {
delete door1;
door1 = 0;
delete door2;
door2 = 0;
}
};
class Door {
public:
Room* roomA;
Room* roomB;
Door(Room* roomA, Room* roomB) {
this->roomA = roomA;
this->roomB = roomB;
linkRooms(); // This sets up the Door-Pointers in the Rooms
// so they know about the new door.
}
~Door() {
// Returns the room-owned pointer pointing at this door
getMyRoomPointer(roomA) = 0;
getMyRoomPointer(roomB) = 0;
}
Door * & getMyRoomPointer(Room * const & room) {
if (room->door1 == this) return room->door1;
else return room->door2;
}
void linkRooms() {
roomA->door1 = this;
roomB->door2 = this;
}
};
class World {
public:
std::set<Room*> rooms;
World() {
// Set up two rooms and link them using a door
Room* newRoom = new Room();
rooms.insert(newRoom);
Room* anotherNewRoom = new Room();
rooms.insert(anotherNewRoom);
new Door(newRoom, anotherNewRoom);
}
~World() {
// Iterate over the rooms and call delete on all of them
for (std::set<Room*>::iterator it = rooms.begin(); it != rooms.end(); ++it) {
delete *it;
}
}
};
int main() {
World world;
return 0;
}
当 运行 主要时,构造函数只用两个房间和一扇门填充世界,作为它们之间的 link。 main returns 之后,world 应该被删除,所有的房间和门也应该被处理。
问题是,我的 Door 析构函数从未被调用过。所以房间内的门指针没有设置为空,当房间 "at the other side" 试图删除同一扇门时我得到一个错误。
当我刚创建一个 Door 实例,然后立即删除它时,我没有遇到任何问题:
int main(){
Room oneRoom;
Room anotherRoom;
Door* door = new Door(&oneRoom, &anotherRoom);
delete door; // works just fine
return 0;
}
问题:为什么不调用 Door 构造函数?是否允许像第一个示例中那样设置门?
我知道,我正在双重删除我房间的门指针,并且我可以(并且应该)使用 SmartPointers。现在我只是想知道为什么我要面对这种行为。毕竟,我还是 C++ 的新手。
我现在确实设置了一个可运行的示例,它重现了错误。
您在定义 Door
之前调用了 delete
。因此程序的行为是未定义的,不能保证调用析构函数。
引自标准(草案)[expr.delete]:
- If the object being deleted has incomplete class type at the point of deletion and the complete class has a non-trivial destructor or a deallocation function, the behavior is undefined.
解决方案:如果类型的析构函数是非平凡的(即用户定义的,例如 ~Door
,则在类型完成之前永远不要删除此类对象)。在这种情况下,在调用 delete 的函数之前定义 Door
。
作为一般规则,除非 class 类型完整,否则永远不能调用成员函数。不幸的是,对于析构函数,编译器可能并不总是能够捕获错误。 PS。 g++ 会警告您的程序:warning: possible problem detected in invocation of delete operator: [-Wdelete-incomplete]
你永远不会删除门
new Door(newRoom, anotherNewRoom);
你应该在世界
中添加Door *door
public:
Door *door;
...
door = new Door(newRoom, anotherNewRoom);
并在world的析构函数中调用delete:
delete door;
请注意,您的代码有很多未定义的行为。
std::set<Room *> rooms;
std::set
是一个关联容器,其中包含一组已排序的键类型的唯一对象。
你应该使用列表:
std::list<Room *> rooms;
您应该注意编译器警告消息:
g++ -std=c++17 -fPIC -g -Wall -Wextra -Wwrite-strings -Wno-parentheses -Wpedantic -Warray-bounds -Weffc++ 41120443.cpp -o 41120443
41120443.cpp:1:15: warning: extra tokens at end of #include directive
#include <set>;
^
41120443.cpp:7:7: warning: ‘class Room’ has pointer data members [-Weffc++]
class Room {
^~~~
41120443.cpp:7:7: warning: but does not override ‘Room(const Room&)’ [-Weffc++]
41120443.cpp:7:7: warning: or ‘operator=(const Room&)’ [-Weffc++]
41120443.cpp: In constructor ‘Room::Room()’:
41120443.cpp:12:5: warning: ‘Room::door1’ should be initialized in the member initialization list [-Weffc++]
Room(){}
^~~~
41120443.cpp:12:5: warning: ‘Room::door2’ should be initialized in the member initialization list [-Weffc++]
41120443.cpp: In destructor ‘Room::~Room()’:
41120443.cpp:15:16: warning: possible problem detected in invocation of delete operator: [-Wdelete-incomplete]
delete door1;
^~~~~
41120443.cpp:15:16: warning: invalid use of incomplete type ‘class Door’
41120443.cpp:5:7: note: forward declaration of ‘class Door’
class Door; // Forward declaration
^~~~
41120443.cpp:15:16: note: neither the destructor nor the class-specific operator delete will be called, even if they are declared when the class is defined
delete door1;
^~~~~
41120443.cpp:17:16: warning: possible problem detected in invocation of delete operator: [-Wdelete-incomplete]
delete door2;
^~~~~
41120443.cpp:17:16: warning: invalid use of incomplete type ‘class Door’
41120443.cpp:5:7: note: forward declaration of ‘class Door’
class Door; // Forward declaration
^~~~
41120443.cpp:17:16: note: neither the destructor nor the class-specific operator delete will be called, even if they are declared when the class is defined
delete door2;
^~~~~
41120443.cpp: At global scope:
41120443.cpp:22:7: warning: ‘class Door’ has pointer data members [-Weffc++]
class Door {
^~~~
41120443.cpp:22:7: warning: but does not override ‘Door(const Door&)’ [-Weffc++]
41120443.cpp:22:7: warning: or ‘operator=(const Door&)’ [-Weffc++]
41120443.cpp: In constructor ‘Door::Door(Room*, Room*)’:
41120443.cpp:27:5: warning: ‘Door::roomA’ should be initialized in the member initialization list [-Weffc++]
Door(Room* roomA, Room* roomB) {
^~~~
41120443.cpp:27:5: warning: ‘Door::roomB’ should be initialized in the member initialization list [-Weffc++]
41120443.cpp: In constructor ‘World::World()’:
41120443.cpp:56:5: warning: ‘World::rooms’ should be initialized in the member initialization list [-Weffc++]
World() {
^~~~~
和 Valgrind 输出:
valgrind ./41120443
==2864== Memcheck, a memory error detector
==2864== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==2864== Using Valgrind-3.12.0.SVN and LibVEX; rerun with -h for copyright info
==2864== Command: ./41120443
==2864==
==2864== Conditional jump or move depends on uninitialised value(s)
==2864== at 0x4C2C291: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==2864== by 0x108BC3: Room::~Room() (41120443.cpp:17)
==2864== by 0x108D67: World::~World() (41120443.cpp:68)
==2864== by 0x108B65: main (41120443.cpp:75)
==2864==
==2864== Conditional jump or move depends on uninitialised value(s)
==2864== at 0x4C2C291: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==2864== by 0x108BA8: Room::~Room() (41120443.cpp:15)
==2864== by 0x108D67: World::~World() (41120443.cpp:68)
==2864== by 0x108B65: main (41120443.cpp:75)
==2864==
==2864== Invalid free() / delete / delete[] / realloc()
==2864== at 0x4C2C2DB: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==2864== by 0x108BC3: Room::~Room() (41120443.cpp:17)
==2864== by 0x108D67: World::~World() (41120443.cpp:68)
==2864== by 0x108B65: main (41120443.cpp:75)
==2864== Address 0x5a82e00 is 0 bytes inside a block of size 16 free'd
==2864== at 0x4C2C2DB: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==2864== by 0x108BA8: Room::~Room() (41120443.cpp:15)
==2864== by 0x108D67: World::~World() (41120443.cpp:68)
==2864== by 0x108B65: main (41120443.cpp:75)
==2864== Block was alloc'd at
==2864== at 0x4C2B21F: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==2864== by 0x108CCE: World::World() (41120443.cpp:63)
==2864== by 0x108B54: main (41120443.cpp:75)
您可以通过明智地使用智能指针让自己的生活变得更轻松:
#include <memory>
#include <set>
class Door; // Forward declaration
struct Room {
std::shared_ptr<Door> door1 = {};
std::shared_ptr<Door> door2 = {};
~Room();
};
struct Door {
const std::weak_ptr<Room> roomA;
const std::weak_ptr<Room> roomB;
Door(std::shared_ptr<Room> roomA, std::shared_ptr<Room> roomB)
: roomA(roomA),
roomB(roomB)
{
roomA->door1 = roomB->door1 = std::shared_ptr<Door>{this};
}
~Door() = default;
};
// Now that Door is complete, we can define ~Room
Room::~Room() = default;
struct World {
std::set<std::shared_ptr<Room>> rooms = {};
World() {
// Set up two rooms and link them using a door
auto newRoom = std::make_shared<Room>();
rooms.insert(newRoom);
auto anotherNewRoom = std::make_shared<Room>();
rooms.insert(anotherNewRoom);
new Door(newRoom, anotherNewRoom);
}
~World() = default;
};
int main() {
World world;
return 0;
}
这构建并干净地运行:
g++ -std=c++17 -fPIC -g -Wall -Wextra -Wwrite-strings -Wno-parentheses -Wpedantic -Warray-bounds -Weffc++ 41120443.cpp -o 41120443
valgrind ./41120443
==3254== Memcheck, a memory error detector
==3254== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==3254== Using Valgrind-3.12.0.SVN and LibVEX; rerun with -h for copyright info
==3254== Command: ./41120443
==3254==
==3254==
==3254== HEAP SUMMARY:
==3254== in use at exit: 0 bytes in 0 blocks
==3254== total heap usage: 7 allocs, 7 frees, 72,952 bytes allocated
==3254==
==3254== All heap blocks were freed -- no leaks are possible
==3254==
==3254== For counts of detected and suppressed errors, rerun with: -v
==3254== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)