c++:聚合、继承和指针

C++: aggregation, inheritance and pointers

本文关键字:指针 继承 聚合 c++      更新时间:2023-10-16

这是一个关于给定问题在c++中特定OO实现的问题:

一个算法有几个变体,比如两个,它们都有一些通用的部分。这些算法(它们的实现,AlgImpl)接收启动选项。它们也接收输入的数据,然后逐个处理。提供启动选项的方式有很多(从文件、从网络、从用户输入),接收数据的方式也有很多(从文件、从设备)。对于不同的数据源,有一个通用的API,类似于选项。

架构应该允许使用任何可用的AlgImpl以及任何可用的选项和数据源。它还应该允许新的AlgImpl和可能的新类型的源选项和数据添加最小或不改变原始代码(说只有两个AlgImpl,两个选项源类型和两个数据源类型)。

下面是我对c++的看法,使用继承、聚合和指针。因为所有的AlgImpl都有一些共同的部分,所以很自然地将它们围绕一个基抽象类组织起来,所以(省略非必要的数据类型)我们有:

class BaseAlgorithm
{
   ...  //Abstract class with common code
};
class SimpleAlgorithmImpl: public BaseAlgorithm
{
   ...
};
class OptimalAlgorithmImpl: public BaseAlgorithm
{
   ...
};
现在,选项和数据源分别具有相同的接口:
class BaseOptionsSource
{
public:
   //Interface
   virtual void GetOptions(Options& opts) = 0;
};
class FileOptionsSource: public BaseOptionsSource
{
   void GetOptions(Options& opts);
   ...
};
class NetworkOptionsSource: public BaseOptionsSource
{
   void GetOptions(Options& opts);
   ...
};
class BaseDataSource
{
public:
   //Interface
   virtual void GetDataChunk(DataChunk& chunk) = 0;
};
class FileDataSource: public BaseDataSource
{
   void GetDataChunk(DataChunk& chunk);
   ...
};
class DeviceDataSource: public BaseDataSource
{
   void GetDataChunk(DataChunk& chunk);
   ...
};

选项和数据源成为BaseAlgorithm:

的成员
class BaseAlgorithm
{
public:
   BaseAlgorithm(BaseDataSource* pDataSrc, BaseOptionsSource* pOptsSrc); 
   BaseDataSource* _pDataSrc;
   BaseOptionsSource* _pOptsSrc;
};
BaseAlgorithm::BaseAlgorithm(BaseDataSource* pDataSrc, BaseOptionsSource* pOptsSrc):
      _pDataSrc(pDataSrc), _pOptsSrc(pOptsSrc)
{   
}

一个特定的算法对象可以这样创建:

DeviceDataSource dataSrc;
NetworkOptionsSource optsSrc;
SimpleAlgorithmImpl simpleAlg(&dataSrc, &optsSrc);

对于新的AlgImpl或新类型的源,它的类应该实现继承的方法。当然,必须有一个"if/else if/…"代码,在创建AlgImpl对象之前显式地选择使用的AlgImpl和源的集合。

源对象也可以在这种情况下被重用,只要AlgImpl对象不管理传递给它的源对象的分配/释放。

你认为在c++中这样做是正确的吗?或者,对于这种可互换的子功能实现,是否存在其他更简单、更灵活或不那么"无问题"的模式?在这种情况下使用指针是不可避免的吗?

看起来很合理。我认为指针是好的,如果你可以确信数据源和选项源是活的在算法的生命周期。我不认为指针是不可避免的。以下是一些其他选项(其中一些是c++ 11):

引用

最好是const引用。如果在构造函数中传递指针,并且不需要它们为空,则可能比指针更安全。

BaseAlgorithm(BaseDataSource& dataSrc,
              BaseOptionsSource& optsSrc) :
    dataSrc_(dataSrc),
    optsSrc_(optsSrc) {}

共享智能指针

如果您不能确信数据源或选项源在算法的生命周期内是活的,请考虑传入std::shared_ptrstd::weak_ptr。如果你想让他们活着,使用std::shared_ptr,如果你想知道他们是否活着,使用std::weak_ptr

BaseAlgorithm(std::weak_ptr<BaseDataSource> dataSrc,
              std::weak_ptr<BaseOptionsSource> optsSrc) :
    dataSrc_(dataSrc), 
    optsSrc(optsSrc) {}
水槽

这取决于这些数据源,选项源和算法是如何创建和使用的,但如果算法获得数据源和/或选项源的所有权并通过unique_ptr传递,可能会使所有权更简单。

BaseAlgorithm(std::unique_ptr<BaseDataSource> dataSrc,
              std::unique_ptr<BaseOptionsSource> optsSrc) :
    dataSrc_(std::move(dataSrc)),
    optsSrc(std::move(optsSrc)){} 

一些小问题:我怀疑out参数是否是获取选项和数据块的最佳方式。即使对于像数据块这样大的东西,也不要害怕按值返回。任何现代编译器都会对副本进行优化,这使得调用代码更容易读写:

Options opts = optsSrc_->GetOptions(opts);
DataChunk data = dataSrc_->GetDataChunk(data);

你可能为了简化你的文章而错过了它们,但我想知道GetOptions和/或GetDataChunk是否可以成为const ?调用它们是否会对源代码进行更改?如果它们可以是const,这意味着算法只需要一个指向源的const指针/引用,这可能会使查找错误稍微容易一些。您谈论重用源对象,如果算法可以对源对象进行更改,这可能不明智。例如,如果反复调用GetDataChunk会发生什么,每次都得到不同的块吗?如果您可以在不同的算法中使用相同的数据源,这可能会产生意想不到的后果。

最后,确保_pOptsSrc和_pDataSrc是protectedprivate而不是public。