Đây có phải là một cách tiếp cận tốt cho hệ thống phân cấp lớp dựa trên nền tảng hình nền trong C ++ không?


9

Tôi có một hệ thống phân cấp lớp mà tôi muốn tách giao diện khỏi việc thực hiện. Giải pháp của tôi là có hai hệ thống phân cấp: hệ thống phân cấp lớp xử lý cho giao diện và hệ thống phân cấp lớp không công khai để thực hiện. Lớp xử lý cơ sở có một con trỏ để thực hiện mà các lớp xử lý dẫn xuất được truyền tới một con trỏ của kiểu dẫn xuất (xem hàm getPimpl()).

Đây là bản phác thảo giải pháp của tôi cho một lớp cơ sở với hai lớp dẫn xuất. Có một giải pháp tốt hơn?

Tệp "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;
    ...
};

Tệp "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(...); }

Những lớp nào trong số này sẽ được nhìn thấy từ bên ngoài thư viện / thành phần? Nếu chỉ Base, một lớp cơ sở trừu tượng bình thường ("giao diện") và các triển khai cụ thể mà không có pimpl có thể là đủ.
D. Jurcau

@ D.Jurcau Tất cả các lớp cơ sở và dẫn xuất sẽ được hiển thị công khai. Rõ ràng, các lớp thực hiện sẽ không.
Steve Emmerson

Tại sao lại u ám? Lớp cơ sở ở vị trí lạ ở đây, nó có thể được thay thế bằng một con trỏ dùng chung với cải tiến an toàn và ít mã hơn.
Basilevs

@Basilevs Tôi không hiểu. Lớp cơ sở công cộng sử dụng thành ngữ pimpl để ẩn việc thực hiện. Tôi không thấy cách thay thế nó bằng một con trỏ dùng chung có thể duy trì hệ thống phân cấp lớp mà không cần truyền hoặc sao chép con trỏ. Bạn có thể cung cấp một ví dụ mã?
Steve Emmerson

Tôi đề xuất để nhân đôi con trỏ, thay vì sao chép downcast.
Basilevs

Câu trả lời:


1

Tôi nghĩ rằng đó là một chiến lược kém để làm cho Derived_1::Implxuất phát từBase::Impl .

Mục đích chính của việc sử dụng thành ngữ Pimpl là để ẩn các chi tiết triển khai của một lớp. Bằng cách cho Derived_1::Implxuất phát từ Base::Impl, bạn đã đánh bại mục đích đó. Bây giờ, không chỉ thực hiện Basephụ thuộc vào Base::Impl, việc thực hiện Derived_1cũng phụ thuộc vào Base::Impl.

Có một giải pháp tốt hơn?

Điều đó phụ thuộc vào những gì đánh đổi được chấp nhận với bạn.

Giải pháp 1

Làm cho Implcác lớp hoàn toàn độc lập. Điều này sẽ ngụ ý rằng sẽ có hai con trỏ tới Implcác lớp - một trong Basevà một khác trong 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;
};

Giải pháp 2

Chỉ hiển thị các lớp như tay cầm. Đừng để lộ các định nghĩa và triển khai lớp.

Tệp tiêu đề công khai:

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

Đây là triển khai nhanh

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

Ưu và nhược điểm

Với cách tiếp cận đầu tiên, bạn có thể xây dựng Derivedcác lớp trong ngăn xếp. Với cách tiếp cận thứ hai, đó không phải là một lựa chọn.

Với cách tiếp cận đầu tiên, bạn phải chịu chi phí cho hai phân bổ động và phân bổ động để xây dựng và phá hủy một Derivedtrong ngăn xếp. Nếu bạn xây dựng và phá hủy một Derivedđối tượng từ heap bạn, sẽ phải chịu chi phí cho một lần phân bổ và phân bổ thêm. Với cách tiếp cận thứ hai, bạn chỉ phải chịu chi phí cho một phân bổ động và một lần phân bổ cho mọi đối tượng.

Với cách tiếp cận đầu tiên, bạn có khả năng sử dụng virtualchức năng thành viên Base. Với cách tiếp cận thứ hai, đó không phải là một lựa chọn.

Đề xuất của tôi

Tôi sẽ đi với giải pháp đầu tiên để tôi có thể sử dụng hệ thống phân cấp lớp và các virtualhàm thành viên Basemặc dù nó đắt hơn một chút.


0

Cải tiến duy nhất tôi có thể thấy ở đây là để các lớp cụ thể xác định trường thực hiện. Nếu các lớp cơ sở trừu tượng cần nó, chúng có thể định nghĩa một thuộc tính trừu tượng dễ thực hiện trong các lớp cụ thể:

Cơ sở.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(...); }

Điều này dường như an toàn hơn với tôi. Nếu bạn có một cây lớn, bạn cũng có thể giới thiệu virtual std::shared_ptr<Impl1> getImpl1() =0ở giữa cây.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.