Ограничить наследование желаемым количеством классов во время компиляции - PullRequest
3 голосов
/ 30 апреля 2009

У нас есть ограничение, согласно которому класс не может выступать в качестве базового класса для более чем 7 классов. Есть ли способ применить указанное выше правило во время компиляции?

Мне известна техника Эндрю Кенига Usable_Lock, предотвращающая наследование класса, но она не удастся, только когда мы попытаемся создать экземпляр класса. Разве это не может быть сделано при получении себя?

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

class AA {
   friend class BB;
   private:
      AA() {}
      ~AA() {}
};

class BB : public AA {

};

class CC : public AA 
{};

Вывод класса CC приведет к предупреждению компилятора о недоступном dtor. Затем мы можем пометить такие предупреждения, как ошибки с использованием настроек компилятора (например, помечать все предупреждения как ошибки), но я не хотел бы полагаться на такие методы.

Другой способ, но для меня выглядит довольно неуклюжим это: -

class B;

class InheritanceRule{
    class A {
    public:
        A() {}
        ~A() {}
    };
    friend class B;
};

class B {
public:
    class C : public InheritanceRule::A
    {};
};


class D : public InheritanceRule::A{};

Вывод класса D будет помечен как ошибка компилятора, а это означает, что все производные классы должны быть выведены внутри класса B. Это позволит по крайней мере проверить количество классов, производных от класса A, но никому не помешает от добавления больше.

Кто-нибудь здесь, у кого есть способ сделать это? Еще лучше, если базовый класс не должен знать, кто его дети.

ПРИМЕЧАНИЕ: класс, который действует как базовый класс, сам может быть создан (он не является абстрактным).

Заранее спасибо,

EDIT-1: согласно комментарию от jon.h, небольшое изменение

// create a template class without a body, so all uses of it fail
template < typename D> 
class AllowedInheritance;

class Derived; // forward declaration
// but allow Derived by explicit specialization 
template<> 
class AllowedInheritance< Derived> {};

template<class T>
class Base : private AllowedInheritance<T> {};

// privately inherit Derived from that explicit specialization    
class Derived : public Base<Derived> {};

// Do the same with class Fail Error
// it has no explicit specialization, so it causes a compiler error
class Fail : public Base<Fail> {}; // this is error

int main()
{   
   Derived d;

   return 0;
}

Ответы [ 4 ]

5 голосов
/ 30 апреля 2009

Извините, я не знаю, как применить любой такой лимит с помощью компилятора.

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

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

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

Любое программирующее "правило", которое говорит, что вы никогда не должны делать x или вы всегда должны делать y, почти всегда неправильно! Обратите внимание на слово «почти» там.

Иногда вам может понадобиться более 7 производных классов - что вы тогда делаете? Прыгай через больше обручей. Кроме того, почему 7? Почему не 6 или 8? Это так произвольно - еще один признак плохого правила.

Если вы должны сделать это, как говорит JP, статический анализ, вероятно, лучше.

2 голосов
/ 30 апреля 2009

Я устал как дерьмо, едва могу держать глаза открытыми, так что, возможно, есть более элегантный способ сделать это, и я, конечно, не поддерживаю причудливую идею, что у Базы должно быть максимум семь подклассов. *

// create a template class without a body, so all uses of it fail
template < typename D, typename B> class AllowedInheritance;


class Base {};
class Derived; // forward declaration

// but allow Derived, Base by explicit specialization 

template<> class AllowedInheritance< Derived, Base> {};

// privately inherit Derived from that explicit specialization    
class Derived : public Base, private AllowedInheritance<Derived, Base> {};


// Do the same with class Compiler Error
// it has no explicit specialization, so it causes a compiler error
class CompileError: public Base, 
     private AllowedInheritance<CompileError, Base> { };

//error: invalid use of incomplete type 
//‘struct AllowedInheritance<CompileError, Base>’


int main() {

   Base b;
   Derived d;
   return 0;
}

Комментарий от jon.h:

Как это остановить, например: class Fail: public Base {}; ? \

Это не так. Но тогда и оригинального примера ОП не было.

К ОП: ваша редакция моего ответа в значительной степени является прямым применением «Любопытно повторяющегося шаблона» Коплиена ]

Я тоже это учел, но проблема в том, что между derived1 : pubic base<derived1> и derived2 : pubic base<derived2> нет отношения наследования, потому что base<derived1> и base<derived2> - это два совершенно не связанных класса.

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

Я думаю, что есть способ получить и наследование, и более чистый синтаксис; как я уже говорил, я очень устал, когда писал свое решение. Если ничего другого, то сделать RealBase базовым классом Base в вашем примере - это быстрое решение.

Вероятно, есть несколько способов убрать это. Но я хочу подчеркнуть, что я согласен с markh44: несмотря на то, что мое решение более чистое, мы все еще загромождаем код в поддержку правила, которое не имеет большого смысла. То, что это можно сделать, не означает, что так должно быть.

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

2 голосов
/ 30 апреля 2009

Множество различных инструментов статического анализа кода предоставляют информацию об иерархии наследования. Вместо того, чтобы пытаться обработать его в своем коде, я бы посмотрел на инструмент, который мог бы установить некоторые правила для иерархии наследования и не выполнить сборку, если эти правила не соблюдаются. Может стоить немного $, и вам, возможно, придется написать собственное правило (я видел глубину наследования, но не "широту" наследования, как вы хотите). Но, в конечном счете, я думаю, что это ваша лучшая ставка.

За комментарий: я использовал Coverity с некоторым успехом. Немного потратил. Есть несколько хороших потоков SO , которые могут иметь лучшие варианты.

1 голос
/ 30 апреля 2009

Вместо того, чтобы загромождать код утверждениями, вы можете использовать что-то вроде GCC-XML , которое анализирует исходный код C ++ с использованием внешнего интерфейса компилятора g ++ и генерирует вывод XML. Я ожидаю, что было бы достаточно просто разработать инструмент, который анализирует этот вывод и проверяет нарушения правила; затем его можно интегрировать с регистрацией исходного кода.

Кстати, знание базовых классов об их потомках нарушает принцип Open-Closed , означающий, что это фактически подрывает полезность ОО-программирования в целом. Основная причина для разделения кода на базовые классы и подклассы заключается в том, что базовый класс не должен знать о своих подклассах - это делает возможными такие вещи, как пакеты подключаемых модулей, доставляемые после установки.

...