Delete Derived Class Through Base Pointer C++ 设计模式
Delete Derived Class Through Base Pointer C++ Design Pattern
现在我不确定我是否只是在这里使用了一个糟糕的设计模式,所以很高兴接受一些关于这方面的意见,但这是我正在尝试做的事情。
我有很多 classes 是按层次结构排列的,但是基础 classes 由于它们的使用方式,不能有虚拟析构函数。 (如果可以的话,事情会容易得多!)本质上,它们只是数据的集合。我遇到的问题是,我需要在这些派生的 class 上创建不同的类型和调用方法,而且(之后)还需要调用基础 class 的方法。您可以通过在每个 switch 案例中调用基本 class 方法来解决这个问题,但这看起来很难看,所以我正在寻找一种在 switch 之外调用它们的方法。我在这里遇到的问题是我有一个指向派生对象的基指针,并且不知道它实际上是哪个派生对象,并且因为基没有虚拟析构函数,如果通过基指针删除它会内存泄漏。这就是我想出的:
class Base
{
public:
Base() { std::cout << "Base constructor called" << std::endl; }
~Base() { std::cout << "Base destructor called" << std::endl; }
void SetTimer(void) {}
void SetStatus(void) {}
void SetLogger(void) {}
protected:
uint32_t data1, data_2;
uint32_t data3;
};
class Derived1 : public Base
{
public:
Derived1() { std::cout << "Derived1 constructor called" << std::endl; }
~Derived1() { std::cout << "Derived1 destructor called" << std::endl; }
void Special1(void) { data1 = data2 = 0; }
};
class Derived2 : public Base
{
public:
~Derived2() { std::cout << "Derived2 destructor called" << std::endl; }
void Special2(void) { data3 = 0; }
};
class Derived3 : public Base
{
public:
~Derived3() { std::cout << "Derived3 destructor called" << std::endl; }
void Special3(void) { data1 = data2 = data3 = 0; }
};
//template<typename T>
//struct deleter
//{
// void operator()(T* p) const { delete p; }
//};
int main(int argc, char** argv)
{
int cl = 1;
{
auto deleter = [](auto* t) { delete static_cast<decltype(t)>(t); };
// std::unique_ptr<Base, deleter<Base*>> d1;
std::unique_ptr<Base, decltype(deleter)> d1(new Base(), deleter);
switch (cl)
{
case 1:
{
d1 = std::unique_ptr<Derived1, decltype(deleter)>(new Derived1(), deleter);
// d1 = std::unique_ptr<Derived1, decltype(deleter<Derived1*>)>(new Derived1(), deleter<Derived1*>());
static_cast<Derived1*>(d1.get())->Special1();
break;
}
case 2:
{
d1 = std::unique_ptr<Derived2, decltype(deleter)>(new Derived2(), deleter);
static_cast<Derived2*>(d1.get())->Special2();
break;
}
case 3:
{
d1 = std::unique_ptr<Derived3, decltype(deleter)>(new Derived3(), deleter);
static_cast<Derived3*>(d1.get())->Special3();
break;
}
}
d1.get()->SetLogger();
d1.get()->SetStatus();
d1.get()->SetTimer();
}
return 0;
}
因此,在每个 case 语句中,我都必须创建一个新的对象类型,但需要一些我以后可以用来调用基方法的东西。正如您在注释掉的代码中看到的那样,我确实考虑过使用仿函数来绕过 unique_ptr<> 声明,但想不出一个让它起作用的好方法。
我最大的问题是这一行:
std::unique_ptr<Base, decltype(deleter)> d1(new Base(), deleter);
只是因为我需要在声明时提供删除器,所以创建一个基 class 似乎不太好。如您所见,我考虑过仿函数方法,但这似乎没有帮助,因为模板仿函数的声明与 std::unique_ptr.
不同
现在我不知道这是代码问题还是我只是选择了一个糟糕的设计模式来做这种事情或者我被注释掉的代码是否可以工作。无论如何,我们将不胜感激。
编辑编辑
感谢您的帮助,但是 class 层次结构中不能有虚拟方法或虚拟析构函数,因为创建的对象必须完全符合标准布局。所以 std::is_standard_layout<>::value 应该 return 为真。因此,虽然我很欣赏人们说没有虚拟析构函数是一个奇怪的要求,但这是我必须处理的。 (哦,它 是 一个没有虚函数的继承的完全合法的用例。)
您有两种方法可以完成此操作。如果你想使用 unique_ptr
你需要制作一个仿函数来枚举所制作的类型,然后它通过一个开关来调用正确版本的删除。这不是最好的解决方案,因为它需要在每次将新类型添加到层次结构时更新枚举和仿函数。您需要这样做的原因是删除器是 unique_ptr
类型的一部分。
另一种选择是改用 std::shared_ptr
。 shared_ptr
的删除器不是类型的一部分,而是内部的一部分。这意味着当你创建一个 shared_ptr
到一个派生的 class 并将它存储在一个 shared_ptr
到基数 class 中时,它会记住它实际上指向一个派生的 class。使用它会让你看起来像
int main(int argc, char** argv)
{
int cl = 1;
{
std::shared_ptr<Base> d1;
switch (cl)
{
case 1:
{
d1 = std::make_shared<Derived1>();
static_cast<Derived1*>(d1.get())->Special1();
break;
}
case 2:
{
d1 = std::make_shared<Derived2>();
static_cast<Derived2*>(d1.get())->Special2();
break;
}
case 3:
{
d1 = std::make_shared<Derived3>();
static_cast<Derived3*>(d1.get())->Special3();
break;
}
}
d1.get()->SetLogger();
d1.get()->SetStatus();
d1.get()->SetTimer();
}
return 0;
}
您最好重写代码以避免这些问题。
示例1:使用函数共享通用代码
void PostProcess(Base &base)
{
base.SetLogger();
base.SetStatus();
base.SetTimer();
}
int main(int argc, char** argv)
{
int cl = 1;
{
switch (cl)
{
case 1:
{
// Example with an object on the stack
Derived1 d1;
d1.Special1();
PostProcess(d1);
break;
}
case 2:
{
// Example using make_unique
auto d2 = std::make_unique<Derived2>();
d2->Special2();
PostProcess(*d2);
break;
}
case 3:
{
// Example similar to your syntax
std::unique_ptr<Derived3> d3(new Derived3());
d3->Special3();
PostProcess(*d3);
break;
}
}
}
return 0;
}
示例2:使用模板共享代码
主要思想是抽象差异,以便可以使用通用代码来处理所有派生类型
// Fix different naming (if you cannot modify existing classes)
void Special(Derived1 &d1) { d1.Special1(); }
void Special(Derived2 &d2) { d2.Special2(); }
void Special(Derived3 &d3) { d3.Special3(); }
// Use a template function to re-use code
template <class T> void Process()
{
auto d = std::make_unique<T>();
Special(*d);
d->SetLogger();
d->SetStatus();
d->SetTimer();
}
届时,您可以像这样修改 switch case:
case 1:
Process<Derived1>();
break;
// Similar code for other cases…
示例 3:创建代理 classes 以获得 OO 设计
想法是拥有一个并行的 class 层次结构,这样您的原始类型仍然遵守您所需的约束,但有一个可以使用 v-table[ 的代理=37=]
class BaseProxy {
public:
virtual ~BaseProxy() { }
virtual void Special() = 0;
};
// Example where you can take a reference to an external object
// that is expected to live longer than the proxy...
class Derived1Proxy : public BaseProxy
{
public:
Derived1Proxy(Derived1 &d1_) : d1(d1_) { }
void Special() override { d1.Special1(); }
private:
Derived1 &d1;
};
// Example where your proxy contain the Derived object
class Derived2Proxy : public BaseProxy
{
public:
Derived2Proxy() { }
void Special() override { d2.Special2(); }
private:
Derived2 d2;
};
// Example where you want to ensure that the derived object live as long as required
class Derived3Proxy : public BaseProxy
{
public:
Derived3Proxy(std::shared_ptr<Derived3> p3_) : p3(p3_) { }
void Special() override { d3->Special3(); }
private:
std::shared_ptr<Derived3> p3;
};
std::unique_ptr<BaseProxy> GetProxy(int c1)
{
case 1:
return std::make_unique<Derived1Proxy>(derived1Object);
// other case...
}
您可以根据需要调整该代码。
现在我不确定我是否只是在这里使用了一个糟糕的设计模式,所以很高兴接受一些关于这方面的意见,但这是我正在尝试做的事情。
我有很多 classes 是按层次结构排列的,但是基础 classes 由于它们的使用方式,不能有虚拟析构函数。 (如果可以的话,事情会容易得多!)本质上,它们只是数据的集合。我遇到的问题是,我需要在这些派生的 class 上创建不同的类型和调用方法,而且(之后)还需要调用基础 class 的方法。您可以通过在每个 switch 案例中调用基本 class 方法来解决这个问题,但这看起来很难看,所以我正在寻找一种在 switch 之外调用它们的方法。我在这里遇到的问题是我有一个指向派生对象的基指针,并且不知道它实际上是哪个派生对象,并且因为基没有虚拟析构函数,如果通过基指针删除它会内存泄漏。这就是我想出的:
class Base
{
public:
Base() { std::cout << "Base constructor called" << std::endl; }
~Base() { std::cout << "Base destructor called" << std::endl; }
void SetTimer(void) {}
void SetStatus(void) {}
void SetLogger(void) {}
protected:
uint32_t data1, data_2;
uint32_t data3;
};
class Derived1 : public Base
{
public:
Derived1() { std::cout << "Derived1 constructor called" << std::endl; }
~Derived1() { std::cout << "Derived1 destructor called" << std::endl; }
void Special1(void) { data1 = data2 = 0; }
};
class Derived2 : public Base
{
public:
~Derived2() { std::cout << "Derived2 destructor called" << std::endl; }
void Special2(void) { data3 = 0; }
};
class Derived3 : public Base
{
public:
~Derived3() { std::cout << "Derived3 destructor called" << std::endl; }
void Special3(void) { data1 = data2 = data3 = 0; }
};
//template<typename T>
//struct deleter
//{
// void operator()(T* p) const { delete p; }
//};
int main(int argc, char** argv)
{
int cl = 1;
{
auto deleter = [](auto* t) { delete static_cast<decltype(t)>(t); };
// std::unique_ptr<Base, deleter<Base*>> d1;
std::unique_ptr<Base, decltype(deleter)> d1(new Base(), deleter);
switch (cl)
{
case 1:
{
d1 = std::unique_ptr<Derived1, decltype(deleter)>(new Derived1(), deleter);
// d1 = std::unique_ptr<Derived1, decltype(deleter<Derived1*>)>(new Derived1(), deleter<Derived1*>());
static_cast<Derived1*>(d1.get())->Special1();
break;
}
case 2:
{
d1 = std::unique_ptr<Derived2, decltype(deleter)>(new Derived2(), deleter);
static_cast<Derived2*>(d1.get())->Special2();
break;
}
case 3:
{
d1 = std::unique_ptr<Derived3, decltype(deleter)>(new Derived3(), deleter);
static_cast<Derived3*>(d1.get())->Special3();
break;
}
}
d1.get()->SetLogger();
d1.get()->SetStatus();
d1.get()->SetTimer();
}
return 0;
}
因此,在每个 case 语句中,我都必须创建一个新的对象类型,但需要一些我以后可以用来调用基方法的东西。正如您在注释掉的代码中看到的那样,我确实考虑过使用仿函数来绕过 unique_ptr<> 声明,但想不出一个让它起作用的好方法。
我最大的问题是这一行:
std::unique_ptr<Base, decltype(deleter)> d1(new Base(), deleter);
只是因为我需要在声明时提供删除器,所以创建一个基 class 似乎不太好。如您所见,我考虑过仿函数方法,但这似乎没有帮助,因为模板仿函数的声明与 std::unique_ptr.
不同现在我不知道这是代码问题还是我只是选择了一个糟糕的设计模式来做这种事情或者我被注释掉的代码是否可以工作。无论如何,我们将不胜感激。
编辑编辑
感谢您的帮助,但是 class 层次结构中不能有虚拟方法或虚拟析构函数,因为创建的对象必须完全符合标准布局。所以 std::is_standard_layout<>::value 应该 return 为真。因此,虽然我很欣赏人们说没有虚拟析构函数是一个奇怪的要求,但这是我必须处理的。 (哦,它 是 一个没有虚函数的继承的完全合法的用例。)
您有两种方法可以完成此操作。如果你想使用 unique_ptr
你需要制作一个仿函数来枚举所制作的类型,然后它通过一个开关来调用正确版本的删除。这不是最好的解决方案,因为它需要在每次将新类型添加到层次结构时更新枚举和仿函数。您需要这样做的原因是删除器是 unique_ptr
类型的一部分。
另一种选择是改用 std::shared_ptr
。 shared_ptr
的删除器不是类型的一部分,而是内部的一部分。这意味着当你创建一个 shared_ptr
到一个派生的 class 并将它存储在一个 shared_ptr
到基数 class 中时,它会记住它实际上指向一个派生的 class。使用它会让你看起来像
int main(int argc, char** argv)
{
int cl = 1;
{
std::shared_ptr<Base> d1;
switch (cl)
{
case 1:
{
d1 = std::make_shared<Derived1>();
static_cast<Derived1*>(d1.get())->Special1();
break;
}
case 2:
{
d1 = std::make_shared<Derived2>();
static_cast<Derived2*>(d1.get())->Special2();
break;
}
case 3:
{
d1 = std::make_shared<Derived3>();
static_cast<Derived3*>(d1.get())->Special3();
break;
}
}
d1.get()->SetLogger();
d1.get()->SetStatus();
d1.get()->SetTimer();
}
return 0;
}
您最好重写代码以避免这些问题。
示例1:使用函数共享通用代码
void PostProcess(Base &base)
{
base.SetLogger();
base.SetStatus();
base.SetTimer();
}
int main(int argc, char** argv)
{
int cl = 1;
{
switch (cl)
{
case 1:
{
// Example with an object on the stack
Derived1 d1;
d1.Special1();
PostProcess(d1);
break;
}
case 2:
{
// Example using make_unique
auto d2 = std::make_unique<Derived2>();
d2->Special2();
PostProcess(*d2);
break;
}
case 3:
{
// Example similar to your syntax
std::unique_ptr<Derived3> d3(new Derived3());
d3->Special3();
PostProcess(*d3);
break;
}
}
}
return 0;
}
示例2:使用模板共享代码
主要思想是抽象差异,以便可以使用通用代码来处理所有派生类型
// Fix different naming (if you cannot modify existing classes)
void Special(Derived1 &d1) { d1.Special1(); }
void Special(Derived2 &d2) { d2.Special2(); }
void Special(Derived3 &d3) { d3.Special3(); }
// Use a template function to re-use code
template <class T> void Process()
{
auto d = std::make_unique<T>();
Special(*d);
d->SetLogger();
d->SetStatus();
d->SetTimer();
}
届时,您可以像这样修改 switch case:
case 1:
Process<Derived1>();
break;
// Similar code for other cases…
示例 3:创建代理 classes 以获得 OO 设计
想法是拥有一个并行的 class 层次结构,这样您的原始类型仍然遵守您所需的约束,但有一个可以使用 v-table[ 的代理=37=]
class BaseProxy {
public:
virtual ~BaseProxy() { }
virtual void Special() = 0;
};
// Example where you can take a reference to an external object
// that is expected to live longer than the proxy...
class Derived1Proxy : public BaseProxy
{
public:
Derived1Proxy(Derived1 &d1_) : d1(d1_) { }
void Special() override { d1.Special1(); }
private:
Derived1 &d1;
};
// Example where your proxy contain the Derived object
class Derived2Proxy : public BaseProxy
{
public:
Derived2Proxy() { }
void Special() override { d2.Special2(); }
private:
Derived2 d2;
};
// Example where you want to ensure that the derived object live as long as required
class Derived3Proxy : public BaseProxy
{
public:
Derived3Proxy(std::shared_ptr<Derived3> p3_) : p3(p3_) { }
void Special() override { d3->Special3(); }
private:
std::shared_ptr<Derived3> p3;
};
std::unique_ptr<BaseProxy> GetProxy(int c1)
{
case 1:
return std::make_unique<Derived1Proxy>(derived1Object);
// other case...
}
您可以根据需要调整该代码。