Bu C ++ 'da "pImpl" tabanlı bir sınıf hiyerarşisi için iyi bir yaklaşım mı?


9

Arabirimi uygulamadan ayırmak istediğim bir sınıf hiyerarşisi var. Benim çözümüm iki hiyerarşiye sahip olmaktır: arabirim için tanıtıcı sınıf hiyerarşisi ve uygulama için ortak olmayan sınıf hiyerarşisi. Temel tanıtıcı sınıfı, türetilmiş tanıtıcı sınıflarının türetilmiş türde bir işaretçiye yayınladığı uygulamaya yönelik bir işaretleyiciye (işleve bakın getPimpl()) sahiptir.

İşte benim türetilmiş iki sınıflı bir temel sınıf için çözümümün bir taslağı. Daha iyi bir çözüm var mı?

"Base.h" dosyası:

#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" dosyası:

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(...); }

Bu sınıflardan hangisi kitaplığın / bileşenin dışından görünür olacak? Sadece Basenormal bir temel sınıf ("arayüz") ve sivilce içermeyen somut uygulamalar yeterli olabilir.
D.Jurcau

@ D.Jurcau Temel ve türetilmiş sınıfların tümü herkes tarafından görülebilir. Açıkçası, uygulama sınıfları olmayacak.
Steve Emmerson

Neden mahzun? Temel sınıf burada garip bir konumda, gelişmiş güvenlik ve daha az kod ile paylaşılan bir işaretçi ile değiştirilebilir.
Basilevs

@Basilevs Anlamıyorum. Ortak taban sınıfı, uygulamayı gizlemek için pimpl deyimini kullanır. Paylaşılan bir işaretçiyle değiştirmenin, işaretçiyi yayınlamadan veya çoğaltmadan sınıf hiyerarşisini nasıl koruyabileceğini görmüyorum. Kod örneği verebilir misiniz?
Steve Emmerson

Uğultuyu kopyalamak yerine işaretçiyi çoğaltmayı öneririm.
Basilevs

Yanıtlar:


1

Bence Derived_1::Impltüretmek kötü bir strateji Base::Impl.

Pimpl deyimini kullanmanın temel amacı, bir sınıfın uygulama ayrıntılarını gizlemektir. İzin vererek Derived_1::Implkaynaklanıyor Base::Impl, bu amaca yendiniz. Şimdi, sadece uygulamasına Basebağlı olmakla kalmıyor, aynı zamanda Base::Impluygulamasına Derived_1da bağlı Base::Impl.

Daha iyi bir çözüm var mı?

Bu, hangi takasların sizin için kabul edilebilir olduğuna bağlıdır.

Çözüm 1

Marka Implsınıfları tamamen bağımsız. Bu iki işaretçi olacağını ima edecek Impltek - sınıflar Baseve başka bir 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;
};

Çözüm 2

Sınıfları yalnızca tanıtıcı olarak gösterin. Sınıf tanımlarını ve uygulamalarını hiçbir şekilde sergilemeyin.

Genel başlık dosyası:

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); 

İşte hızlı uygulama

#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
}

Lehte ve aleyhte olanlar

İlk yaklaşımla, Derivedyığın içinde sınıflar oluşturabilirsiniz. İkinci yaklaşımla, bu bir seçenek değildir.

İlk yaklaşımla, Derivedyığındaki bir inşası ve imhası için iki dinamik ayırma ve ayırma maliyetine katlanırsınız . Bir Derivednesneyi öbekten inşa edip yok ederseniz, bir tahsis ve yeniden yerleştirmenin maliyetine katlanın. İkinci yaklaşımla, her nesne için yalnızca bir dinamik ayırma ve bir ayırma maliyetine katlanırsınız.

İlk yaklaşımla virtualüye fonksiyonunu kullanabilme yeteneğine sahip olursunuz Base. İkinci yaklaşımla, bu bir seçenek değildir.

Benim önerim

Sınıf hiyerarşisini ve virtualüye işlevlerini Basebiraz daha pahalı olmasına rağmen kullanabilmem için ilk çözümle birlikte giderdim .


0

Burada görebildiğim tek gelişme, somut sınıfların uygulama alanını tanımlamasına izin vermektir. Soyut temel sınıfların buna ihtiyacı varsa, somut sınıflarda uygulanması kolay soyut bir özellik tanımlayabilirler:

base.h

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(...); }

Bu benim için daha güvenli görünüyor. Büyük bir ağacınız varsa virtual std::shared_ptr<Impl1> getImpl1() =0, ağacın ortasına da tanıtabilirsiniz .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.