Khi nào tôi nên sử dụng thừa kế riêng C ++?


116

Không giống như thừa kế được bảo vệ, thừa kế riêng của C ++ được tìm thấy trong quá trình phát triển C ++ chính thống. Tuy nhiên, tôi vẫn chưa tìm thấy một cách sử dụng tốt cho nó.

Khi nào các bạn sử dụng nó?

c++  oop 

Câu trả lời:


60

Lưu ý sau khi chấp nhận câu trả lời: Đây KHÔNG phải là một câu trả lời hoàn chỉnh. Đọc các câu trả lời khác như ở đây (về mặt khái niệm) và ở đây (cả lý thuyết và thực tiễn) nếu bạn quan tâm đến câu hỏi. Đây chỉ là một thủ thuật ưa thích có thể đạt được với thừa kế tư nhân. Trong khi nó là lạ mắt, nó không phải là câu trả lời cho câu hỏi.

Bên cạnh việc sử dụng cơ bản chỉ thừa kế riêng tư được hiển thị trong Câu hỏi thường gặp về C ++ (được liên kết trong các nhận xét khác), bạn có thể sử dụng kết hợp thừa kế riêng và ảo để đóng dấu một lớp (theo thuật ngữ .NET) hoặc để tạo một lớp cuối cùng (theo thuật ngữ Java) . Đây không phải là một cách sử dụng phổ biến, nhưng dù sao tôi cũng thấy nó thú vị:

class ClassSealer {
private:
   friend class Sealed;
   ClassSealer() {}
};
class Sealed : private virtual ClassSealer
{ 
   // ...
};
class FailsToDerive : public Sealed
{
   // Cannot be instantiated
};

Niêm phong có thể được khởi tạo. Nó xuất phát từ ClassSealer và có thể gọi trực tiếp đến nhà xây dựng riêng vì đây là một người bạn.

FailsToDerive sẽ không biên dịch vì nó phải gọi ClassSealer constructor trực tiếp (yêu cầu thừa kế ảo), nhưng nó không thể vì nó là tư nhân trong Sealed lớp và trong trường hợp này FailsToDerive không phải là một người bạn của ClassSealer .


BIÊN TẬP

Nó đã được đề cập trong các ý kiến ​​rằng điều này không thể được thực hiện chung chung tại thời điểm sử dụng CRTP. Tiêu chuẩn C ++ 11 loại bỏ giới hạn đó bằng cách cung cấp một cú pháp khác để kết bạn với các đối số mẫu:

template <typename T>
class Seal {
   friend T;          // not: friend class T!!!
   Seal() {}
};
class Sealed : private virtual Seal<Sealed> // ...

Tất nhiên đây là tất cả, vì C ++ 11 cung cấp một finaltừ khóa theo ngữ cảnh cho chính xác mục đích này:

class Sealed final // ...

Đó là một kỹ thuật tuyệt vời. Tôi sẽ viết một mục blog trên đó.

1
Câu hỏi: nếu chúng tôi không sử dụng kế thừa ảo, thì FailsToDerive sẽ biên dịch. Chính xác?

4
+1. @Sasha: Đúng, thừa kế ảo là cần thiết vì lớp có nguồn gốc nhất luôn gọi các hàm tạo của tất cả các lớp hầu như được kế thừa trực tiếp, đây không phải là trường hợp thừa kế đơn giản.
j_random_hacker 18/03/2016

5
Điều này có thể được tạo thành chung chung, mà không cần tạo ClassSealer tùy chỉnh cho mọi lớp bạn muốn đóng dấu! Hãy xem thử: class ClassSealer {bảo vệ: ClassSealer () {}}; đó là tất cả.

+1 Margarimbilanja, rất tuyệt! BTW Tôi đã thấy nhận xét trước đó của bạn (hiện đã bị xóa) về việc sử dụng CRTP: Tôi nghĩ rằng trên thực tế nên hoạt động, thật khó để có được cú pháp cho bạn bè mẫu đúng. Nhưng trong mọi trường hợp, giải pháp phi mẫu của bạn tuyệt vời hơn nhiều :)
j_random_hacker 18/03/2016

138

Tôi sử dụng nó mọi lúc. Một vài ví dụ ngoài đỉnh đầu của tôi:

  • Khi tôi muốn phơi bày một số nhưng không phải tất cả giao diện của lớp cơ sở. Kế thừa công khai sẽ là một lời nói dối, vì khả năng thay thế Liskov bị phá vỡ, trong khi thành phần có nghĩa là viết một loạt các chức năng chuyển tiếp.
  • Khi tôi muốn xuất phát từ một lớp cụ thể mà không có hàm hủy ảo. Kế thừa công khai sẽ mời khách hàng xóa thông qua một con trỏ đến cơ sở, gọi hành vi không xác định.

Một ví dụ điển hình là xuất phát riêng từ một container STL:

class MyVector : private vector<int>
{
public:
    // Using declarations expose the few functions my clients need 
    // without a load of forwarding functions. 
    using vector<int>::push_back;
    // etc...  
};
  • Khi triển khai Mẫu bộ điều hợp, kế thừa riêng từ lớp Thích nghi sẽ phải chuyển tiếp đến một thể hiện kèm theo.
  • Để thực hiện một giao diện riêng. Điều này xuất hiện thường xuyên với Mẫu quan sát. Điển hình là lớp Người quan sát của tôi, MyClass nói, đăng ký chính nó với một số Chủ đề. Sau đó, chỉ MyClass cần thực hiện chuyển đổi MyClass -> Observer. Phần còn lại của hệ thống không cần biết về nó, do đó, kế thừa riêng được chỉ định.

4
@Krsna: Thật ra, tôi không nghĩ vậy. Chỉ có một lý do duy nhất ở đây: sự lười biếng, ngoài việc cuối cùng, sẽ khó khăn hơn để làm việc xung quanh.
Matthieu M.

11
Không quá lười biếng (trừ khi bạn có nghĩa là theo cách tốt). Điều này cho phép tạo ra sự quá tải mới của các chức năng đã được đưa ra mà không cần làm thêm. Nếu trong C ++ 1x họ thêm 3 lần quá tải mới push_back, hãy MyVectornhận chúng miễn phí.
David Stone

@DavidStone, bạn có thể làm điều đó với một phương thức mẫu không?
Julien__

5
@Julien__: Có, bạn có thể viết template<typename... Args> constexpr decltype(auto) f(Args && ... args) noexcept(noexcept(std::declval<Base &>().f(std::forward<Args>(args)...)) and std::is_nothrow_move_constructible<decltype(std::declval<Base &>().f(std::forward<Args>(args)...))>) { return m_base.f(std::forward<Args>(args)...); }hoặc bạn có thể viết bằng cách sử dụng Base::f;. Nếu bạn muốn hầu hết các chức năng và tính linh hoạt mà thừa kế riêng tư và một usingtuyên bố mang lại cho bạn, bạn có con quái vật đó cho từng chức năng (và đừng quên constvolatilequá tải!).
David Stone

2
Tôi nói hầu hết các chức năng bởi vì bạn vẫn đang gọi thêm một hàm tạo di chuyển không có trong phiên bản câu lệnh sử dụng. Nói chung, bạn sẽ mong đợi điều này sẽ được tối ưu hóa, nhưng về mặt lý thuyết, chức năng có thể trả về một loại không thể di chuyển theo giá trị. Mẫu chức năng chuyển tiếp cũng có thêm phần khởi tạo mẫu và độ sâu constexpr. Điều này có thể khiến chương trình của bạn chạy vào giới hạn thực hiện.
David Stone

31

Cách sử dụng chính thức của thừa kế riêng là mối quan hệ "được thực hiện theo nghĩa" (nhờ vào 'C ++ hiệu quả' của Scott Meyers cho cách diễn đạt này). Nói cách khác, giao diện bên ngoài của lớp kế thừa không có mối quan hệ (hiển thị) với lớp được kế thừa, nhưng nó sử dụng nó bên trong để thực hiện chức năng của nó.


6
Có thể đáng nói đến một trong những lý do tại sao nó được sử dụng trong trường hợp này: Điều này cho phép thực hiện tối ưu hóa lớp cơ sở trống, điều này sẽ không xảy ra nếu lớp đó là thành viên thay vì lớp cơ sở.
jalf

2
công dụng chính của nó là giảm mức tiêu thụ không gian khi thực sự quan trọng, ví dụ như trong các lớp chuỗi được kiểm soát chính sách hoặc theo cặp nén. trên thực tế, boost :: compression_ Pair đã sử dụng quyền thừa kế được bảo vệ.
Julian Schaub - litb 17/03/2016

Jalf: Này, tôi đã không nhận ra điều đó. Tôi nghĩ rằng thừa kế không công khai chủ yếu được sử dụng như một hack khi bạn cần truy cập vào các thành viên được bảo vệ của một lớp. Tôi tự hỏi tại sao một đối tượng trống sẽ chiếm bất kỳ không gian khi sử dụng thành phần mặc dù. Có lẽ đối với định địa chỉ phổ biến ...

3
Nó cũng thuận tiện để làm cho một lớp không thể bị phát hiện - đơn giản là kế thừa một cách riêng tư từ một lớp trống không thể sao chép được. Bây giờ bạn không phải trải qua công việc bận rộn khi khai báo nhưng không xác định hàm tạo và gán toán tử sao chép riêng. Meyers nói về điều này, quá.
Michael Burr 17/03/2016

tôi đã không nhận ra câu hỏi này thực sự là về thừa kế tư nhân thay vì thừa kế được bảo vệ. vâng tôi đoán có khá nhiều ứng dụng cho nó. không thể nghĩ ra nhiều ví dụ cho thừa kế được bảo vệ mặc dù: / có vẻ như điều đó hiếm khi hữu ích.
Julian Schaub - litb 17/03/2016

23

Một cách sử dụng hữu ích của kế thừa riêng là khi bạn có một lớp thực hiện giao diện, sau đó được đăng ký với một số đối tượng khác. Bạn đặt giao diện đó ở chế độ riêng tư để lớp phải đăng ký và chỉ đối tượng cụ thể mà nó đã đăng ký mới có thể sử dụng các hàm đó.

Ví dụ:

class FooInterface
{
public:
    virtual void DoSomething() = 0;
};

class FooUser
{
public:
    bool RegisterFooInterface(FooInterface* aInterface);
};

class FooImplementer : private FooInterface
{
public:
    explicit FooImplementer(FooUser& aUser)
    {
        aUser.RegisterFooInterface(this);
    }
private:
    virtual void DoSomething() { ... }
};

Do đó, lớp FooUser có thể gọi các phương thức riêng của FooImâyer thông qua giao diện FooInterface, trong khi các lớp bên ngoài khác không thể. Đây là một mẫu tuyệt vời để xử lý các cuộc gọi lại cụ thể được xác định là giao diện.


1
Thật vậy, thừa kế tư nhân là tư nhân IS-A.
tò mò

18

Tôi nghĩ phần quan trọng trong C ++ FAQ Lite là:

Việc sử dụng hợp pháp, lâu dài cho thừa kế riêng là khi bạn muốn xây dựng một lớp Fred sử dụng mã trong một lớp Wilma và mã từ lớp Wilma cần phải gọi các hàm thành viên từ lớp mới của bạn, Fred. Trong trường hợp này, Fred gọi những thứ không phải là ảo trong Wilma và Wilma gọi (thường là ảo thuần túy), được Fred ghi đè. Điều này sẽ khó hơn nhiều để làm với thành phần.

Nếu nghi ngờ, bạn nên thích sáng tác hơn thừa kế tư nhân.


4

Tôi thấy nó hữu ích cho các giao diện (viz. Các lớp trừu tượng) mà tôi đang kế thừa ở nơi tôi không muốn mã khác chạm vào giao diện (chỉ lớp kế thừa).

[chỉnh sửa trong một ví dụ]

Lấy ví dụ liên kết ở trên. Nói rằng

[...] Lớp Wilma cần gọi các hàm thành viên từ lớp mới của bạn, Fred.

là để nói rằng Wilma đang yêu cầu Fred có thể gọi một số chức năng thành viên nhất định, hay nói đúng hơn là Wilma là một giao diện . Do đó, như đã đề cập trong ví dụ

thừa kế tư nhân không xấu xa; Nó chỉ tốn kém hơn để duy trì, vì nó làm tăng khả năng ai đó sẽ thay đổi thứ gì đó sẽ phá vỡ mã của bạn.

nhận xét về hiệu ứng mong muốn của các lập trình viên cần đáp ứng các yêu cầu giao diện của chúng tôi hoặc phá mã. Và, vì fredCallsWilma () chỉ được bảo vệ bạn bè và các lớp dẫn xuất có thể chạm vào nó, tức là một giao diện được kế thừa (lớp trừu tượng) mà chỉ lớp kế thừa mới có thể chạm vào (và bạn bè).

[chỉnh sửa trong một ví dụ khác]

Trang này thảo luận ngắn gọn về các giao diện riêng tư (từ một góc độ khác).


Không thực sự nghe có vẻ hữu ích ... bạn có thể gửi một ví dụ

Tôi nghĩ rằng tôi thấy bạn đang đi đâu ... Một trường hợp sử dụng điển hình có thể là Wilma là một loại lớp tiện ích cần gọi các hàm ảo trong Fred, nhưng các lớp khác không cần biết rằng Fred được triển khai theo thuật ngữ- của Wilma. Đúng?
j_random_hacker 18/03/2016

Đúng. Tôi nên chỉ ra rằng, theo cách hiểu của tôi, thuật ngữ 'giao diện' được sử dụng phổ biến hơn trong Java. Khi tôi nghe về nó lần đầu tiên tôi đã nghĩ nó có thể được đặt tên tốt hơn. Vì trong ví dụ này, chúng ta có một giao diện mà không ai giao tiếp theo cách mà chúng ta thường nghĩ về từ này.
thiên vị

@Noos: Có Tôi nghĩ rằng tuyên bố của bạn "Wilma là một giao diện" hơi mơ hồ, vì hầu hết mọi người sẽ coi điều này có nghĩa là Wilma là một giao diện mà Fred dự định cung cấp cho thế giới , thay vì chỉ là hợp đồng với Wilma.
j_random_hacker

@j_ Đó là lý do tại sao tôi nghĩ giao diện là một tên xấu. Giao diện, thuật ngữ, không có nghĩa là với thế giới như mọi người nghĩ, mà là một sự đảm bảo về chức năng. Trên thực tế, tôi đã tranh cãi về giao diện hạn trong lớp Thiết kế chương trình của mình. Nhưng, chúng tôi sử dụng những gì chúng tôi được đưa ra ...
thiên vị

2

Đôi khi tôi thấy hữu ích khi sử dụng thừa kế riêng tư khi tôi muốn hiển thị một giao diện nhỏ hơn (ví dụ: bộ sưu tập) trong giao diện của một giao diện khác, trong đó việc triển khai bộ sưu tập yêu cầu quyền truy cập vào trạng thái của lớp phơi bày, theo cách tương tự với các lớp bên trong Java.

class BigClass;

struct SomeCollection
{
    iterator begin();
    iterator end();
};

class BigClass : private SomeCollection
{
    friend struct SomeCollection;
    SomeCollection &GetThings() { return *this; }
};

Sau đó, nếu someCollection cần truy cập BigClass, nó có thể static_cast<BigClass *>(this). Không cần phải có thêm một thành viên dữ liệu chiếm không gian.


Không cần khai báo về phía trước BigClasstrong ví dụ này? Tôi thấy điều này thú vị, nhưng nó gào thét vào mặt tôi.
Thomas Eding

2

Tôi tìm thấy một ứng dụng đẹp cho thừa kế riêng tư, mặc dù nó có hạn sử dụng.

Vấn đề cần giải quyết

Giả sử bạn được cung cấp API C sau:

#ifdef __cplusplus
extern "C" {
#endif

    typedef struct
    {
        /* raw owning pointer, it's C after all */
        char const * name;

        /* more variables that need resources
         * ...
         */
    } Widget;

    Widget const * loadWidget();

    void freeWidget(Widget const * widget);

#ifdef __cplusplus
} // end of extern "C"
#endif

Bây giờ công việc của bạn là triển khai API này bằng C ++.

Cách tiếp cận C-ish

Tất nhiên chúng ta có thể chọn kiểu triển khai C-ish như vậy:

Widget const * loadWidget()
{
    auto result = std::make_unique<Widget>();
    result->name = strdup("The Widget name");
    // More similar assignments here
    return result.release();
}

void freeWidget(Widget const * const widget)
{
    free(result->name);
    // More similar manual freeing of resources
    delete widget;
}

Nhưng có một số nhược điểm:

  • Quản lý tài nguyên thủ công (ví dụ bộ nhớ)
  • Thật dễ dàng để thiết lập structsai
  • Thật dễ dàng để quên giải phóng tài nguyên khi giải phóng struct
  • Đó là C-ish

Cách tiếp cận C ++

Chúng tôi được phép sử dụng C ++, vậy tại sao không sử dụng toàn bộ sức mạnh của nó?

Giới thiệu quản lý tài nguyên tự động

Các vấn đề trên về cơ bản đều gắn liền với việc quản lý tài nguyên thủ công. Giải pháp xuất hiện trong đầu là kế thừa từ Widgetvà thêm một thể hiện quản lý tài nguyên vào lớp dẫn xuất WidgetImplcho mỗi biến:

class WidgetImpl : public Widget
{
public:
    // Added bonus, Widget's members get default initialized
    WidgetImpl()
        : Widget()
    {}

    void setName(std::string newName)
    {
        m_nameResource = std::move(newName);
        name = m_nameResource.c_str();
    }

    // More similar setters to follow

private:
    std::string m_nameResource;
};

Điều này giúp đơn giản hóa việc thực hiện như sau:

Widget const * loadWidget()
{
    auto result = std::make_unique<WidgetImpl>();
    result->setName("The Widget name");
    // More similar setters here
    return result.release();
}

void freeWidget(Widget const * const widget)
{
    // No virtual destructor in the base class, thus static_cast must be used
    delete static_cast<WidgetImpl const *>(widget);
}

Như thế này, chúng tôi đã khắc phục tất cả các vấn đề trên. Nhưng một khách hàng vẫn có thể quên đi các setters WidgetImplvà gán cho các Widgetthành viên trực tiếp.

Thừa kế tư nhân bước vào giai đoạn

Để gói gọn các Widgetthành viên, chúng tôi sử dụng thừa kế tư nhân. Đáng buồn thay, bây giờ chúng ta cần hai hàm bổ sung để truyền giữa cả hai lớp:

class WidgetImpl : private Widget
{
public:
    WidgetImpl()
        : Widget()
    {}

    void setName(std::string newName)
    {
        m_nameResource = std::move(newName);
        name = m_nameResource.c_str();
    }

    // More similar setters to follow

    Widget const * toWidget() const
    {
        return static_cast<Widget const *>(this);
    }

    static void deleteWidget(Widget const * const widget)
    {
        delete static_cast<WidgetImpl const *>(widget);
    }

private:
    std::string m_nameResource;
};

Điều này làm cho các điều chỉnh sau đây cần thiết:

Widget const * loadWidget()
{
    auto widgetImpl = std::make_unique<WidgetImpl>();
    widgetImpl->setName("The Widget name");
    // More similar setters here
    auto const result = widgetImpl->toWidget();
    widgetImpl.release();
    return result;
}

void freeWidget(Widget const * const widget)
{
    WidgetImpl::deleteWidget(widget);
}

Giải pháp này giải quyết tất cả các vấn đề. Không quản lý bộ nhớ thủ công và Widgetđược đóng gói độc đáo để WidgetImplkhông còn thành viên dữ liệu nào nữa. Nó làm cho việc thực hiện dễ sử dụng một cách chính xác và khó (không thể?) Để sử dụng sai.

Đoạn mã tạo thành một ví dụ biên dịch trên Coliru .


1

Nếu lớp dẫn xuất - cần sử dụng lại mã và - bạn không thể thay đổi lớp cơ sở và - đang bảo vệ các phương thức của nó bằng cách sử dụng các thành viên của cơ sở dưới khóa.

sau đó bạn nên sử dụng kế thừa riêng, nếu không bạn có nguy cơ các phương thức cơ sở đã mở khóa được xuất qua lớp dẫn xuất này.


1

Đôi khi nó có thể là một sự thay thế cho tập hợp , ví dụ nếu bạn muốn tổng hợp nhưng với hành vi thay đổi của thực thể tổng hợp (ghi đè các hàm ảo).

Nhưng bạn nói đúng, nó không có nhiều ví dụ từ thế giới thực.


0

Kế thừa riêng được sử dụng khi quan hệ không phải là "là một", nhưng lớp mới có thể được "triển khai theo thuật ngữ của lớp hiện tại" hoặc lớp mới "hoạt động như" lớp hiện có.

ví dụ từ "Tiêu chuẩn mã hóa C ++ của Andrei Alexandrescu, Herb Sutter": - Hãy xem xét rằng hai lớp Square và Hình chữ nhật đều có các hàm ảo để đặt chiều cao và chiều rộng của chúng. Sau đó, Square không thể kế thừa chính xác từ Hình chữ nhật, bởi vì mã sử dụng Hình chữ nhật có thể sửa đổi sẽ cho rằng SetWidth không thay đổi chiều cao (dù hình chữ nhật có rõ ràng là hợp đồng hay không), trong khi Square :: SetWidth không thể bảo vệ hợp đồng đó và bất biến vuông góc của chính nó tại cùng lúc. Nhưng Hình chữ nhật cũng không thể kế thừa chính xác từ Square, nếu khách hàng của Square cho rằng ví dụ diện tích của Square là bình phương chiều rộng hoặc nếu họ dựa vào một số thuộc tính khác không giữ cho Hình chữ nhật.

Hình chữ nhật "is-a" hình vuông (về mặt toán học) nhưng Hình vuông không phải là Hình chữ nhật (theo hành vi). Do đó, thay vì "is-a", chúng tôi muốn nói "works-like-a" (hoặc, nếu bạn thích, "usable-as-a") để làm cho mô tả ít bị hiểu lầm.


0

Một lớp giữ một bất biến. Bất biến được thiết lập bởi các nhà xây dựng. Tuy nhiên, trong nhiều trường hợp, thật hữu ích khi có chế độ xem trạng thái đại diện của đối tượng (mà bạn có thể truyền qua mạng hoặc lưu vào tệp - DTO nếu bạn muốn). REST được thực hiện tốt nhất dưới dạng AggregateType. Điều này đặc biệt đúng nếu bạn đúng. Xem xét:

struct QuadraticEquationState {
   const double a;
   const double b;
   const double c;

   // named ctors so aggregate construction is available,
   // which is the default usage pattern
   // add your favourite ctors - throwing, try, cps
   static QuadraticEquationState read(std::istream& is);
   static std::optional<QuadraticEquationState> try_read(std::istream& is);

   template<typename Then, typename Else>
   static std::common_type<
             decltype(std::declval<Then>()(std::declval<QuadraticEquationState>()),
             decltype(std::declval<Else>()())>::type // this is just then(qes) or els(qes)
   if_read(std::istream& is, Then then, Else els);
};

// this works with QuadraticEquation as well by default
std::ostream& operator<<(std::ostream& os, const QuadraticEquationState& qes);

// no operator>> as we're const correct.
// we _might_ (not necessarily want) operator>> for optional<qes>
std::istream& operator>>(std::istream& is, std::optional<QuadraticEquationState>);

struct QuadraticEquationCache {
   mutable std::optional<double> determinant_cache;
   mutable std::optional<double> x1_cache;
   mutable std::optional<double> x2_cache;
   mutable std::optional<double> sum_of_x12_cache;
};

class QuadraticEquation : public QuadraticEquationState, // private if base is non-const
                          private QuadraticEquationCache {
public:
   QuadraticEquation(QuadraticEquationState); // in general, might throw
   QuadraticEquation(const double a, const double b, const double c);
   QuadraticEquation(const std::string& str);
   QuadraticEquation(const ExpressionTree& str); // might throw
}

Tại thời điểm này, bạn có thể chỉ lưu trữ các bộ sưu tập bộ đệm trong các thùng chứa và tra cứu nó khi xây dựng. Tiện dụng nếu có một số xử lý thực sự. Lưu ý rằng bộ đệm là một phần của QE: các thao tác được xác định trên QE có thể có nghĩa là bộ đệm có thể được sử dụng lại một phần (ví dụ: c không ảnh hưởng đến tổng); Tuy nhiên, khi không có bộ nhớ cache, đáng để tìm kiếm nó.

Kế thừa tư nhân hầu như luôn có thể được mô hình hóa bởi một thành viên (lưu trữ tham chiếu đến cơ sở nếu cần). Không phải lúc nào nó cũng xứng đáng để mô hình theo cách đó; đôi khi thừa kế là đại diện hiệu quả nhất.


0

Nếu bạn cần một std::ostreamvài thay đổi nhỏ (như trong câu hỏi này ), bạn có thể cần phải

  1. Tạo một lớp MyStreambufxuất phát từ std::streambufvà thực hiện các thay đổi ở đó
  2. Tạo một lớp MyOStreamxuất phát từ std::ostreamđó cũng khởi tạo và quản lý một thể hiện của MyStreambufvà chuyển con trỏ đến thể hiện đó cho hàm tạo củastd::ostream

Ý tưởng đầu tiên có thể là thêm MyStreamcá thể làm thành viên dữ liệu vào MyOStreamlớp:

class MyOStream : public std::ostream
{
public:
    MyOStream()
        : std::basic_ostream{ &m_buf }
        , m_buf{}
    {}

private:
    MyStreambuf m_buf;
};

Nhưng các lớp học cơ sở được xây dựng trước khi bất kỳ thành viên dữ liệu, do đó bạn đang đi qua một con trỏ đến một chưa xây dựng std::streambufví dụ để std::ostreamđó là hành vi không xác định.

Giải pháp được đề xuất trong câu trả lời của Ben cho câu hỏi đã nói ở trên , đơn giản là kế thừa từ bộ đệm luồng trước, sau đó từ luồng và sau đó khởi tạo luồng với this:

class MyOStream : public MyStreamBuf, public std::ostream
{
public:
    MyOStream()
        : MyStreamBuf{}
        , basic_ostream{ this }
    {}
};

Tuy nhiên, lớp kết quả cũng có thể được sử dụng như một std::streambufthể hiện thường không mong muốn. Chuyển sang thừa kế riêng giải quyết vấn đề này:

class MyOStream : private MyStreamBuf, public std::ostream
{
public:
    MyOStream()
        : MyStreamBuf{}
        , basic_ostream{ this }
    {}
};

-1

Chỉ vì C ++ có một tính năng, không có nghĩa là nó hữu ích hoặc nó nên được sử dụng.

Tôi muốn nói rằng bạn không nên sử dụng nó.

Dù sao đi nữa, nếu bạn đang sử dụng nó, thì về cơ bản, bạn đang vi phạm đóng gói và hạ thấp sự gắn kết. Bạn đang đặt dữ liệu vào một lớp và thêm các phương thức thao tác dữ liệu vào một lớp khác.

Giống như các tính năng C ++ khác, nó có thể được sử dụng để đạt được các tác dụng phụ như niêm phong một lớp (như được đề cập trong câu trả lời của dribeas), nhưng điều này không làm cho nó trở thành một tính năng tốt.


bạn đang mỉa mai? tất cả những gì tôi có là -1! anyway tôi sẽ không xóa này ngay cả khi nó được -100 phiếu
Hasen

9
" Về cơ bản bạn đang vi phạm đóng gói " Bạn có thể đưa ra một ví dụ không?
tò mò

1
dữ liệu trong một lớp học và hành vi trong một âm thanh giống như sự gia tăng tính linh hoạt, vì có thể có nhiều hơn một lớp hành vi và các khách hàng và chọn cái nào họ cần phải đáp ứng những gì họ muốn
Makar
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.