继承和智能指针 (std::shared_ptr)
Inheritance and smart pointers (std::shared_ptr)
有很多话要说。首先,我想知道下面的方法是否被认为是一种设计模式,甚至是一种常用技术(这就是为什么我没有提供有关标题的更多信息)。如果是这样的话,名字是什么?
无论如何,这是我想要实现的目标的缩小版本。由于我需要使用复制,我发现使用 std::shared_ptr 是避免释放(删除)指针的最佳方法。
class Foo
{
public:
Foo() : ptr(nullptr) {}
Foo(const Foo& foo) : ptr(foo.ptr) {}
virtual ~Foo() = default;
void whatever() {
if (ptr)
ptr->whateverHandler();
}
void reset() {
ptr.reset();
}
void resetBar() {
ptr.reset(new Bar);
}
// Other resets here...
protected:
Foo(Foo* foo) : ptr(foo) {}
private:
// Every child class should override this
virtual void whateverHandler() {
throw "whateverHandler cant be called within base class";
}
protected:
std::shared_ptr<Foo> ptr;
};
class Bar : public Foo
{
public:
Bar() : Foo(this) {}
void whateverHandler() {
printf("Bar's handler!!! \n");
}
};
这一切看起来都很好,编译也很好,但是,下面的测试崩溃了。这是为什么?
int main()
{
{
Foo f;
f.resetBar();
}
return getchar();
}
Bar() : Foo(this) {}
将 this
传递给 shared_ptr
时要小心。
再想想f.resetBar();
和ptr.reset(new Bar);
之后会发生什么。
对于new Bar
,会构造一个Bar
类型的对象,并在其构造函数内部this
传递给父class成员ptr
,则该对象由 std::shared_ptr
.
it 管理
之后对象由f.ptr
管理;这是另一个 std::shared_ptr
.
所以有两个 std::shared_ptr
指向同一个对象,但是 std::shared_ptr
对此一无所知;因为你正在分别构建它们。当 f
和 f.ptr
被销毁时, pointed-to 对象也会被销毁。然后成员ptr
将被销毁,它会尝试再次销毁同一个对象,这导致UB。
我不确定设计试图完成什么,但只要停止将 this
传递给 std::shared_ptr
就可以消除 UB。
class Foo
{
public:
virtual ~Foo() = default;
void whatever() {
if (ptr)
ptr->whateverHandler();
}
void reset() {
ptr.reset();
}
void resetBar() {
ptr.reset(new Bar);
}
// Other resets here...
private:
// Every child class should override this
virtual void whateverHandler() = 0;
std::shared_ptr<Foo> ptr;
};
class Bar : public Foo
{
public:
void whateverHandler() {
printf("Bar's handler!!! \n");
}
};
int main()
{
{
Foo f;
f.resetBar();
f.whatever();
f.resetSthElse();
f.whatever();
}
}
和 IMO,有一个 std::shared_ptr
类型的成员指向派生的 class 是令人困惑的;分开可能更好。然后,我认为可能是 bridge design partern.
class Foo
{
public:
void whatever() {
if (ptr)
ptr->whateverHandler();
}
void reset() {
ptr.reset();
}
void resetBar() {
ptr.reset(new BarHandler);
}
// Other resets here...
private:
std::shared_ptr<FooHandler> ptr;
};
class FooHandler
{
public:
virtual ~FooHandler() = default;
// Every child class should override this
virtual void whateverHandler() = 0;
};
class BarHandler : public FooHandler
{
public:
void whateverHandler() {
printf("Bar's handler!!! \n");
}
};
int main()
{
{
Foo f;
f.resetBar();
f.whatever();
f.resetSthElse();
f.whatever();
}
}
Foo::ptr
持有一个指向其母亲 Foo(this)
且引用计数为 1 的指针。
在Foo::resetBar()
中,当Foo要求Foo::ptr调用reset(new Bar)
时,Foo::ptr 放弃了它的所有权给它的母亲 Foo(this)
并且发现引用计数已经减少到 0,所以它需要杀死 Foo。
当Foo
死亡时,它的children也被杀死。所以 Foo::ptr 肯定也死了。然后将 new Bar
分配给 dead Foo::ptr
会导致 UB.
有很多话要说。首先,我想知道下面的方法是否被认为是一种设计模式,甚至是一种常用技术(这就是为什么我没有提供有关标题的更多信息)。如果是这样的话,名字是什么? 无论如何,这是我想要实现的目标的缩小版本。由于我需要使用复制,我发现使用 std::shared_ptr 是避免释放(删除)指针的最佳方法。
class Foo
{
public:
Foo() : ptr(nullptr) {}
Foo(const Foo& foo) : ptr(foo.ptr) {}
virtual ~Foo() = default;
void whatever() {
if (ptr)
ptr->whateverHandler();
}
void reset() {
ptr.reset();
}
void resetBar() {
ptr.reset(new Bar);
}
// Other resets here...
protected:
Foo(Foo* foo) : ptr(foo) {}
private:
// Every child class should override this
virtual void whateverHandler() {
throw "whateverHandler cant be called within base class";
}
protected:
std::shared_ptr<Foo> ptr;
};
class Bar : public Foo
{
public:
Bar() : Foo(this) {}
void whateverHandler() {
printf("Bar's handler!!! \n");
}
};
这一切看起来都很好,编译也很好,但是,下面的测试崩溃了。这是为什么?
int main()
{
{
Foo f;
f.resetBar();
}
return getchar();
}
Bar() : Foo(this) {}
将 this
传递给 shared_ptr
时要小心。
再想想f.resetBar();
和ptr.reset(new Bar);
之后会发生什么。
对于
new Bar
,会构造一个Bar
类型的对象,并在其构造函数内部this
传递给父class成员ptr
,则该对象由std::shared_ptr
. it 管理
之后对象由
f.ptr
管理;这是另一个std::shared_ptr
.
所以有两个 std::shared_ptr
指向同一个对象,但是 std::shared_ptr
对此一无所知;因为你正在分别构建它们。当 f
和 f.ptr
被销毁时, pointed-to 对象也会被销毁。然后成员ptr
将被销毁,它会尝试再次销毁同一个对象,这导致UB。
我不确定设计试图完成什么,但只要停止将 this
传递给 std::shared_ptr
就可以消除 UB。
class Foo
{
public:
virtual ~Foo() = default;
void whatever() {
if (ptr)
ptr->whateverHandler();
}
void reset() {
ptr.reset();
}
void resetBar() {
ptr.reset(new Bar);
}
// Other resets here...
private:
// Every child class should override this
virtual void whateverHandler() = 0;
std::shared_ptr<Foo> ptr;
};
class Bar : public Foo
{
public:
void whateverHandler() {
printf("Bar's handler!!! \n");
}
};
int main()
{
{
Foo f;
f.resetBar();
f.whatever();
f.resetSthElse();
f.whatever();
}
}
和 IMO,有一个 std::shared_ptr
类型的成员指向派生的 class 是令人困惑的;分开可能更好。然后,我认为可能是 bridge design partern.
class Foo
{
public:
void whatever() {
if (ptr)
ptr->whateverHandler();
}
void reset() {
ptr.reset();
}
void resetBar() {
ptr.reset(new BarHandler);
}
// Other resets here...
private:
std::shared_ptr<FooHandler> ptr;
};
class FooHandler
{
public:
virtual ~FooHandler() = default;
// Every child class should override this
virtual void whateverHandler() = 0;
};
class BarHandler : public FooHandler
{
public:
void whateverHandler() {
printf("Bar's handler!!! \n");
}
};
int main()
{
{
Foo f;
f.resetBar();
f.whatever();
f.resetSthElse();
f.whatever();
}
}
Foo::ptr
持有一个指向其母亲 Foo(this)
且引用计数为 1 的指针。
在Foo::resetBar()
中,当Foo要求Foo::ptr调用reset(new Bar)
时,Foo::ptr 放弃了它的所有权给它的母亲 Foo(this)
并且发现引用计数已经减少到 0,所以它需要杀死 Foo。
当Foo
死亡时,它的children也被杀死。所以 Foo::ptr 肯定也死了。然后将 new Bar
分配给 dead Foo::ptr
会导致 UB.