Хорошая практика для разработки ABC (абстрактный базовый класс) в C ++ - PullRequest
3 голосов
/ 06 марта 2012

В Java мы можем определить различные интерфейсы, а затем мы можем реализовать несколько интерфейсов для конкретного класса.

// Simulate Java Interface in C++
/*
interface IOne {
    void   MethodOne(int i);
    .... more functions
}

interface ITwo {
    double MethodTwo();
    ... more functions
}

class ABC implements IOne, ITwo {
    // implement MethodOne and MethodTwo
}
*/

Вообще говоря, в C ++ мы должны избегать использования множественного наследования, хотя множественное наследование имеет свои преимущества в некоторых ситуациях.

class ABC {
public:
    virtual void   MethodOne(int /*i*/) = 0 {}
    virtual double MethodTwo() = 0 {}

    virtual ~ABC() = 0 {}

protected:
    ABC() {} // ONLY ABC or subclass can access it
};

Question1 > Основываясь на дизайне ABC, я должен улучшить какие-либо другие вещи, чтобы сделать его приличной азбукой?

Question2 > Правда ли, что товар ABC не должен содержать переменных-членов и вместо этого переменные должны храниться в подклассах?

Question3 > Как я указывал в комментариях, что если ABC должен содержать слишком много чистых функций? Есть ли лучший способ?

Ответы [ 4 ]

9 голосов
/ 06 марта 2012
  1. Не предоставляйте реализацию для чисто виртуальных методов, если в этом нет необходимости.
  2. Не делайте ваш деструктор чисто виртуальным.
  3. Не защищайте конструктор.Вы не можете создать экземпляр абстрактного класса.
  4. Лучше скрыть реализацию конструктора и деструктора внутри исходного файла, чтобы не загрязнять другие объектные файлы.
  5. Сделайте ваш интерфейс не подлежащим копированию.

Если это интерфейс, лучше не иметь там никаких переменных.В противном случае это будет абстрактный базовый класс, а не интерфейс.

Слишком много чистых функций - это нормально, если вы не можете сделать это с менее чистыми функциями.

9 голосов
/ 06 марта 2012

Вообще говоря, в C ++ мы должны избегать использования множественного наследования

Как и любая другая языковая функция, вы должны использовать множественное наследование везде, где это уместно.Интерфейсы обычно считаются подходящим использованием множественного наследования (см., Например, COM).

Конструктор ABC не нуждается в защите - он не может быть создан напрямую, потому что он абстрактный.

Деструктор ABC не должен быть объявлен как чисто виртуальный (конечно, он должен быть объявлен как виртуальный).Вы не должны требовать, чтобы производные классы реализовывали объявленный пользователем конструктор, если он им не нужен.

Интерфейс не должен иметь никакого состояния и, следовательно, не должен иметь никаких переменных-членов, потому что интерфейс только определяет, как что-тодолжен использоваться, а не как это должно быть реализовано.

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

4 голосов
/ 06 марта 2012

Основываясь на дизайне ABC, я должен улучшить что-либо еще, чтобы сделать его достойным ABC?

У вас есть пара синтаксических ошибок. По какой-то причине вы не можете поместить определение чисто виртуальной функции в определение класса; и в любом случае вы почти наверняка не хотите определять их в азбуке. Поэтому декларации обычно бывают:

virtual void MethodOne(int /*i*/) = 0;   // ";" not "{}" - just a declaration

Нет смысла делать деструктор чистым, хотя он должен быть виртуальным (или, в некоторых случаях, не виртуальным и защищенным - но безопаснее всего сделать его виртуальным).

virtual ~ABC() {}  // no "= 0"

Нет необходимости в защищенном конструкторе - тот факт, что он является абстрактным, уже предотвращает создание экземпляров, кроме как в качестве базового класса.

Правда ли, что хороший ABC не должен содержать переменных-членов и вместо этого переменные должны храниться в подклассах?

Обычно да. Это дает четкое разделение между интерфейсом и реализацией.

Как я указал в комментариях, что если ABC должен содержать слишком много чистых функций? Есть ли лучший способ?

Интерфейс должен быть настолько сложным, насколько это необходимо, и не более. Есть только «слишком много» функций, если некоторые из них не нужны; в этом случае избавьтесь от них. Если интерфейс выглядит слишком сложным, возможно, он пытается сделать больше, чем одну вещь; в этом случае вы сможете разбить его на более мелкие интерфейсы, каждый с одной целью.

3 голосов
/ 06 марта 2012

Первое: почему мы должны избегать множественного наследования в C ++?Я никогда не видел большого приложения, которое не использовало бы его широко.Наследование от нескольких интерфейсов - хороший пример того, где оно используется.

Обратите внимание, что Java interface не работает - как только вы захотите использовать программирование по контракту, вы застряли с использованием абстрактных классов, и они не допускают множественного наследования.В C ++, однако, это просто:

class One : boost::noncopyable
{
    virtual void doFunctionOne( int i ) = 0;
public:
    virtual ~One() {}
    void functionOne( int i )
    {
        //  assert pre-conditions...
        doFunctionOne( i );
        //  assert post-conditions...
    }
};

class Two : boost::noncopyable
{
    virtual double doFunctionTwo() = 0;
public:
    virtual ~Two() {}
    double functionTwo()
    {
        //  assert pre-conditions...
        double results = doFunctionTwo();
        //  assert post-conditions...
        return results;
    }
};

class ImplementsOneAndTwo : public One, public Two
{
    virtual void doFunctionOne( int i );
    virtual double doFunctionTwo();
public:
};

В качестве альтернативы вы можете иметь составной интерфейс:

class OneAndTwo : public One, public Two
{
};

class ImplementsOneAndTwo : public OneAndTwo
{
    virtual void doFunctionOne( int i );
    virtual double doFunctionTwo();
public:
};

и наследовать его, что всегда имеет смысл.

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

Наконец, обратите внимание, что во многих случаях (особенно если класс представляет значение), вы просто реализуете его напрямую, без интерфейса.В отличие от Java, вам не нужен отдельный интерфейс для поддержки реализации в файле, отличном от определения класса - так работает C ++ по умолчанию (с определением класса в заголовке, но с кодом реализации в исходном файле).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...