这是C ++中基于“ pImpl”的类层次结构的好方法吗?


9

我有一个我希望将接口与实现分开的类层次结构。我的解决方案是有两个层次结构:接口的句柄类层次结构和实现的非公共类层次结构。基本句柄类具有一个指向实现的指针,派生的句柄类将其强制转换为派生类型的指针(请参见function getPimpl())。

这是我对带有两个派生类的基类的解决方案的草图。有更好的解决方案吗?

文件“ Base.h”:

#include <memory>

class Base {
protected:
    class Impl;
    std::shared_ptr<Impl> pImpl;
    Base(Impl* pImpl) : pImpl{pImpl} {};
    ...
};

class Derived_1 final : public Base {
protected:
    class Impl;
    inline Derived_1* getPimpl() const noexcept {
        return reinterpret_cast<Impl*>(pImpl.get());
    }
public:
    Derived_1(...);
    void func_1(...) const;
    ...
};

class Derived_2 final : public Base {
protected:
    class Impl;
    inline Derived_2* getPimpl() const noexcept {
        return reinterpret_cast<Impl*>(pImpl.get());
    }
public:
    Derived_2(...);
    void func_2(...) const;
    ...
};

文件“ Base.cpp”:

class Base::Impl {
public:
    Impl(...) {...}
    ...
};

class Derived_1::Impl final : public Base::Impl {
public:
    Impl(...) : Base::Impl(...) {...}
    void func_1(...) {...}
    ...
};

class Derived_2::Impl final : public Base::Impl {
public:
    Impl(...) : Base::Impl(...) {...}
    void func_2(...) {...}
    ...
};

Derived_1::Derived_1(...) : Base(new Derived_1::Impl(...)) {...}
Derived_1::func_1(...) const { getPimpl()->func_1(...); }

Derived_2::Derived_2(...) : Base(new Derived_2::Impl(...)) {...}
Derived_2::func_2(...) const { getPimpl()->func_2(...); }

从库/组件的外部可以看到以下哪些类?如果只有Base,一个普通的抽象基类(“接口”)和没有pimpl的具体实现可能就足够了。
D. Jurcau

@ D.Jurcau基类和派生类都将公开可见。显然,实现类不会。
史蒂夫·埃默森

为什么垂头丧气?基类在这里处于奇怪的位置,可以用具有改进的类型安全性和更少代码的共享指针替换它。
巴思列夫斯(Basilevs)

@Basilevs我不明白。公共基类使用pimpl习惯用法来隐藏实现。我看不到用共享指针替换它如何在不强制转换或复制指针的情况下维护类的层次结构。您可以提供一个代码示例吗?
史蒂夫·埃默森

我建议复制指针,而不是复制向下转换。
Basilevs

Answers:


1

我认为这是一个贫穷的战略,使Derived_1::Impl从派生Base::Impl

使用Pimpl习惯用法的主要目的是隐藏类的实现细节。通过Derived_1::Impl从派生Base::Impl,您已经击败了这个目的。现在,不仅实现Base依赖于Base::Impl,实现Derived_1也依赖于Base::Impl

有更好的解决方案吗?

这取决于您可以接受哪些折衷。

解决方案1

使Impl类完全独立。这意味着将有两个指向Impl类的指针-一个指针Base和一个另一个指针Derived_N

class Base {

   protected:
      Base() : pImpl{new Impl()} {}

   private:
      // It's own Impl class and pointer.
      class Impl { };
      std::shared_ptr<Impl> pImpl;

};

class Derived_1 final : public Base {
   public:
      Derived_1() : Base(), pImpl{new Impl()} {}
      void func_1() const;
   private:
      // It's own Impl class and pointer.
      class Impl { };
      std::shared_ptr<Impl> pImpl;
};

解决方案2

仅将类公开为手柄。根本不要公开类的定义和实现。

公开头文件:

struct Handle {unsigned long id;};
struct Derived1_tag {};
struct Derived2_tag {};

Handle constructObject(Derived1_tag tag);
Handle constructObject(Derived2_tag tag);

void deleteObject(Handle h);

void fun(Handle h, Derived1_tag tag);
void bar(Handle h, Derived2_tag tag); 

快速实施

#include <map>

class Base
{
   public:
      virtual ~Base() {}
};

class Derived1 : public Base
{
};

class Derived2 : public Base
{
};

namespace Base_Impl
{
   struct CompareHandle
   {
      bool operator()(Handle h1, Handle h2) const
      {
         return (h1.id < h2.id);
      }
   };

   using ObjectMap = std::map<Handle, Base*, CompareHandle>;

   ObjectMap& getObjectMap()
   {
      static ObjectMap theMap;
      return theMap;
   }

   unsigned long getNextID()
   {
      static unsigned id = 0;
      return ++id;
   }

   Handle getHandle(Base* obj)
   {
      auto id = getNextID();
      Handle h{id};
      getObjectMap()[h] = obj;
      return h;
   }

   Base* getObject(Handle h)
   {
      return getObjectMap()[h];
   }

   template <typename Der>
      Der* getObject(Handle h)
      {
         return dynamic_cast<Der*>(getObject(h));
      }
};

using namespace Base_Impl;

Handle constructObject(Derived1_tag tag)
{
   // Construct an object of type Derived1
   Derived1* obj = new Derived1;

   // Get a handle to the object and return it.
   return getHandle(obj);
}

Handle constructObject(Derived2_tag tag)
{
   // Construct an object of type Derived2
   Derived2* obj = new Derived2;

   // Get a handle to the object and return it.
   return getHandle(obj);
}

void deleteObject(Handle h)
{
   // Get a pointer to Base given the Handle.
   //
   Base* obj = getObject(h);

   // Remove it from the map.
   // Delete the object.
   if ( obj != nullptr )
   {
      getObjectMap().erase(h);
      delete obj;
   }
}

void fun(Handle h, Derived1_tag tag)
{
   // Get a pointer to Derived1 given the Handle.
   Derived1* obj = getObject<Derived1>(h);
   if ( obj == nullptr )
   {
      // Problem.
      // Decide how to deal with it.

      return;
   }

   // Use obj
}

void bar(Handle h, Derived2_tag tag)
{
   Derived2* obj = getObject<Derived2>(h);
   if ( obj == nullptr )
   {
      // Problem.
      // Decide how to deal with it.

      return;
   }

   // Use obj
}

利弊

使用第一种方法,您可以Derived在堆栈中构造类。对于第二种方法,这不是一种选择。

采用第一种方法时,您需要Derived在栈中构造和销毁两个动态分配和释放的成本。如果您Derived从堆中构造和销毁一个对象,则将产生分配和释放的费用。使用第二种方法,您只需为每个对象花费一次动态分配和一次重新分配的成本。

通过第一种方法,您可以使用virtual成员函数is Base。对于第二种方法,这不是一种选择。

我的建议

我将采用第一个解决方案,因此即使它稍微贵一点virtualBase也可以使用类层次结构和成员函数。


0

我在这里看到的唯一改进是让具体的类定义了实现字段。如果抽象基类需要它,则可以定义一个易于在具体类中实现的抽象属性:

基数

class Base {
protected:
    class Impl;
    virtual std::shared_ptr<Impl> getImpl() =0;
    ...
};

class Derived_1 final : public Base {
protected:
    class Impl1;
    std::shared_ptr<Impl1> pImpl
    virtual std::shared_ptr<Base::Impl> getImpl();
public:
    Derived_1(...);
    void func_1(...) const;
    ...
};

Base.cpp

class Base::Impl {
public:
    Impl(...) {...}
    ...
};

class Derived_1::Impl1 final : public Base::Impl {
public:
    Impl(...) : Base::Impl(...) {...}
    void func_1(...) {...}
    ...
};

std::shared_ptr<Base::Impl> Derived_1::getImpl() { return pPimpl; }
Derived_1::Derived_1(...) : pPimpl(std::make_shared<Impl1>(...)) {...}
void Derived_1::func_1(...) const { pPimpl->func_1(...); }

这对我来说似乎更安全。如果您有一棵大树,也可以virtual std::shared_ptr<Impl1> getImpl1() =0在树的中间进行介绍。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.