如何解决为抽象基类的一个数据成员获取多态行为的最佳实践
我想知道从软件设计的角度来看,对于每个派生类都应该具有不同类型的多态数据成员的情况,什么是好的方法。更详细:
我正在编写一个具有抽象基类 Base
的库,库的用户将从该类继承。对于 Base
的一个成员,我们称之为 BaseMember
,我想要多态行为。我的意思是从 Base
派生的各种类将“包含”BaseMember
的不同子类 - 有些将包含 OneDerivedMember
,其他将包含 AnotherDerivedMember
等(所有其中一些派生自 BaseMember
,并且所有这些都在库中提供)。想要这样做的原因是,我希望能够查看一些 Base
指针集合并激活 BaseMember
的某些功能(对于其不同的派生类,它的实现方式不同)。据我了解,我猜我必须使 BaseMember
成为一个指针。现在我的问题开始了:
假设基本方法没问题:
-
分配
BaseMember
指针的正确位置在哪里?在各种派生类的构造函数中? -
我可以强制派生类实际执行此分配吗?即,如果用户不理解或忘记他们需要分配一种或另一种
SomeDerivedMember
并使BaseMember
指针指向它,该怎么办?在这种情况下如何强制它不编译? -
该成员应该在哪里释放(解除分配)?我想 RAII 方法规定它将在它被分配的相同范围内(所以,派生类的析构函数?)但这迫使库的每个用户记住进行这种取消分配。相反,我可以在
Base
的析构函数中执行此操作(即在库中,而不是由用户执行) - 但这会违反 RAII 原则吗?如果用户 DID 决定取消分配它(双重删除...)怎么办? -
除此之外,您能想象一种甚至不使用动态分配就具有等效多态行为的方法吗?此代码适用于低级嵌入式 MCU、Cortex M4 或类似内核和裸机(无操作系统) - 因此我尽量避免使用动态分配。
我觉得这种情况一定很常见,会有一种设计模式可以干净利落地解决这个问题,但我不确定那会是什么。
示例代码:
#include <iostream>
#include <list>
using namespace std;
// --------------- Library.h ---------------
class BaseMember {
public:
virtual void do_stuff() = 0;
};
class OneDerivedMember : public BaseMember {
void do_stuff() {/* do stuff one way */}
};
class AnotherDerivedMember : public BaseMember {
void do_stuff() {/* do stuff another way */}
};
class Base {
public:
BaseMember* member;
virtual ~Base() {/* delete member here or not? */}
};
// ------------- User of library ---------------
#include "Library.h"
class Derived : public Base {
public:
Derived() {member = new OneDerivedMember;} // does it make sense to allocate member here?
~Derived() {delete member;} // delete here? or in Base?
};
class CluelessUserDerived : public Base {
public:
CluelessUserDerived() {/* oh,I should have been allocating something here? didn't kNow */}
};
// I want to be able to do that sort of thing,which lead to the above (questionable?) design
int main() {
list<Base*> my_list = {new Derived,new CluelessUserDerived};
for (auto it = my_list.begin(); it != my_list.end(); it++) {
(*it)->member->do_stuff();
}
return 0;
}
解决方法
EDIT 按照 OP 的建议,我将示例替换为完全可运行的示例
我会让界面难以被滥用:
#include <memory>
#include <list>
#include <iostream>
struct BaseMember
{
virtual void do_stuff()
{
std::cout << "BaseMember::do_stuff" << std::endl;
}
virtual ~BaseMember() {}
};
//consider declaring these two classes final
struct YourDefaulHere : BaseMember
{
virtual void do_stuff()
{
std::cout << "YourDefaulHere::do_stuff" << std::endl;
};
virtual ~YourDefaulHere() {}
};
class WithSomeValue : public BaseMember
{
double f;
public:
WithSomeValue(double v) : f(v) {}
virtual void do_stuff()
{
std::cout << "WithSomeValue::do_stuff " << f << std::endl;
};
virtual ~WithSomeValue() {}
};
class Base {
std::unique_ptr<BaseMember> member;
public:
explicit Base(std::unique_ptr<BaseMember> m) : member(std::move(m)) {}
Base() : member(std::make_unique<YourDefaulHere>()) {}
void do_stuff() { member->do_stuff(); }
virtual ~Base() {}
};
//in the client code
class DerivedDefaulted : public Base
{
public:
DerivedDefaulted() {}
};
class DerivedWithSomeValue : public Base
{
public:
DerivedWithSomeValue(std::unique_ptr<BaseMember> m) :
Base(std::move(m)) {}
};
int main() {
//consider using a smart pointer here
std::list<Base*> my_list = {
new DerivedDefaulted(),new DerivedWithSomeValue(std::make_unique<WithSomeValue>(5.0))
};
for (auto it = my_list.begin(); it != my_list.end(); it++) {
(*it)->do_stuff();
}
return 0;
}
输出:
YourDefaulHere::do_stuff
WithSomeValue::do_stuff 5
你甚至可以提供一个工厂方法来创建 std::unique_ptr
您还有两个选项可以在代码中引入多态行为。
传递一个函数
它可能不适合您的情况,但您可以简单地将 std::function
编译时多态
这在标准库中被广泛使用,std::string 就是一个例子。部分行为委托给一个类(称为特征)。 https://en.cppreference.com/w/cpp/string/char_traits
Alexandrescu 的这本书详细介绍了这个想法 https://en.wikipedia.org/wiki/Modern_C%2B%2B_Design
这本书有些陈旧了,有些技术已经过时了,但它仍然是一本很棒的书。
这是一个解释这个想法的小例子:
#include <iostream>
struct Lock
{
Lock() { std::cout << "Acquire lock" << std::endl; }
~Lock() { std::cout << "Release lock" << std::endl; }
};
struct NoAction {};
template<typename MultithreadPolicy>
struct Foo
{
void somethingWithSharedResource()
{
MultithreadPolicy m;
std::cout << "something here" << std::endl;
}
};
typedef Foo<NoAction> NoThreadSafeFoo;
typedef Foo<Lock> LockingFoo;
int main()
{
{
NoThreadSafeFoo f;
f.somethingWithSharedResource();
}
{
LockingFoo f;
f.somethingWithSharedResource();
}
}
有一些限制,最明显的是:
- 没有运行时插件,一切都必须在编译时知道
- 您必须提供库的源代码(仅标头库)
- 二进制文件大小和编译时间可能会增加
另一方面,您可以获得更好的运行时性能,一些计算可以在编译时完成,运行时成本为零,并且您最终(通常)将处理对象和引用而不是指针。
>现代 C++ 肯定经常使用模板库(Boost 就是一个很好的例子)。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。