Использование частного наследования, как описано в "Языке программирования C++"

В языке программирования C++, 4-е издание, в §20.5.2 "Доступ к базовому классу" (стр. 605), говорится (в отношении частного наследования):

частные базы наиболее полезны при определении класса путем ограничения интерфейса базой, чтобы можно было обеспечить более строгие гарантии. Например, B - это деталь реализации Z . Шаблон Vector of pointers, который добавляет проверку типа к своей базе Vector из §25,3 хороший пример.

Непонятно, что Бьярн Страуструп пытается сказать здесь. Как можно определить класс, ограничив "интерфейс" базой? Что он подразумевает под "более сильными гарантиями"?

4 ответа

Давайте возьмем очень простой пример:

// A simple class with a *public* member
class A
{
public:
    int a;
};

// Use private inheritance
class B : private A
{
public:
    int b;
};

// Use public inheritance
class C : public A
{
public:
    int c;
};

// ...

B my_b;
my_b.a = 0;  // Invalid, the member a is private due to the private inhericance

C my_c;
my_c.a = 0;  // Valid, because the inheritance is public

private наследование ограничивает доступ к членам базового класса. Даже если A::a переменная-член public, из-за private наследство становится private в подклассе B,

Остановимся на примере вектора. Вектор - это просто контейнер Ts. Теперь предположим, что вы хотите создать тип, который ведет себя как вектор, но добавляет некоторые дополнительные проверки во время выполнения. У меня сейчас нет моей копии TC++PL, поэтому давайте просто создадим ограничение: например, допустим, что вашему вектору разрешено хранить только четные числа. Попытка вставить нечетное число приведет к ошибке во время выполнения. Давайте назовем этот новый класс even_vector и версия без проверок во время выполнения base_vector,

even_vector обеспечивает более надежные гарантии, чем base_vector: Гарантируется, что все его элементы являются четными.

Предполагая, что ваш base_vector предназначен для работы в качестве базового класса (который std::vector как правило, не), вы могли бы теперь испытать желание использовать публичное наследование для реализации even_vector с точки зрения base_vector, В конце концов, функциональность та же, у вас просто есть несколько дополнительных проверок во время выполнения в even_vector случай на вершине функциональности, предоставляемой base_vector, Однако, если бы вы использовали здесь публичное наследование, вы бы нарушили принцип подстановки Лискова: вы не можете использовать even_vector везде, где вы используете base_vector, В частности, even_vector сломается в тех случаях, когда вы вставляете нечетные числа в base_vector, Это плохо, так как теперь весь код, который написан для base_vector должны учитывать тот факт, что некоторые из base_vectors не может иметь дело с нечетными числами.

С частным наследством у вас нет этой проблемы: вот тот факт, что even_vector наследуется от base_vector это деталь реализации. Клиенты не могут использовать even_vector где base_vector Ожидается, поэтому проблема сверху не возникает, но мы все же получаем преимущества повторного использования кода.

При этом использование частного наследования для повторного использования кода является практикой, которая не поощряется многими. Возможно, лучшим способом было бы использовать здесь композицию, то есть добавить base_vector участник even_vector вместо. Преимущество такого подхода состоит в том, что он значительно снижает связь между двумя классами, так как even_vector больше не может получить доступ к частным частям base_vector,

Как можно определить класс, ограничив "интерфейс" для базы?

Делая наследство частным. Когда наследование является закрытым, интерфейс базового класса ограничен функциями-членами и недоступен извне. Спецификатор доступа может быть указан в списке баз:

class A : private B
//        ^^^^^^^

Что он подразумевает под "более сильными гарантиями"?

Любая гарантия, которая не предоставляется базой, или является расширенной гарантией, предоставляемой базой.

Например, гарантия того, что "поведение всегда четко определено", сильнее, чем "поведение хорошо определено, только если входные данные не равны нулю". Другой пример: "Функция не выбрасывает" сильнее, чем "Функция не будет выбрасывать, если не сгенерирует конструктор копирования".

Позвольте нам взглянуть на возможную ситуацию с интерфейсами, чтобы помочь создать картину.

class poison {
  public:
    virtual void used() = 0;
};

class food {
  public:
    virtual void eat();
  protected:
    void poisonConsumed(poison& p);
}

class cheese : public food, private poison {
  public:
    virtual void eat() override {
      poisonConsumed(*this);
    }
  private:
    void used() override;
}

Это представляет сыр для внешнего мира как "не яд" - то есть ничто вне класса не может знать, что это яд, и его можно сделать "не ядом", чтобы не повлиять на что-либо, использующее его.

Сыр, тем не менее, может передать себя чему-либо, ожидающему яд, который затем можно свободно использовать used(); хотя это частное в сыре.

Другие вопросы по тегам