2009-06-16 2 views
11

У меня есть класс значений в соответствии с описанием в «Стандартах кодирования C++», п. 32. Короче говоря, это означает, что он предоставляет семантику значений и не имеет виртуальных методов.Можно ли запретить вывод из класса во время компиляции?

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

Я не знаю возможности написать класс значений, так что из него невозможно извлечь. Я хочу запретить его во время компиляции. Есть ли какая-нибудь известная идиома? Если нет, возможно, есть новые возможности в предстоящем C++ 0x? Или есть веские причины, что такой возможности нет?

ответ

6

Даже если вопрос не помечен для C++ 11, для людей, которые сюда попадают, следует упомянуть, что C++ 11 поддерживает новый контекстный идентификатор final. См. wiki page

8

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

class underivable { 
    underivable() { } 
    underivable(const underivable&); // not implemented 
    underivable& operator=(const underivable&); // not implemented 
public: 
    static underivable create() { return underivable(); } 
}; 
+0

Ага, и это может быть статический метод фабрики моего класса, чтобы держать вещи вместе? Звучит очень хорошо. – SebastianK

+3

Unfoprto, к счастью, это не останавливает вывод.Это просто предотвращает создание производного типа, за исключением фабрики. Было бы неплохо, если бы вы могли на самом деле pprevent деривации, но, насколько мне известно, вы не можете. – 2009-06-16 11:43:41

+0

В каком практическом смысле существует разница между невозможностью создания объекта производного типа и невозможности получения вообще (доступ к защищенным статическим членам?) – Motti

25

Бьярне Страуструп написал об этом here.


Соответствующий бит по ссылке:

Могу ли я остановить людей, вытекающие из моего класса?

Да, но зачем вы хотите? Есть два общих ответа:

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

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

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

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

class Usable; 

class Usable_lock { 
    friend class Usable; 
private: 
    Usable_lock() {} 
    Usable_lock(const Usable_lock&) {} 
}; 

class Usable : public virtual Usable_lock { 
    // ... 
public: 
    Usable(); 
    Usable(char*); 
    // ... 
}; 

Usable a; 

class DD : public Usable { }; 

DD dd; // error: DD::DD() cannot access 
     // Usable_lock::Usable_lock(): private member 

(from D&E sec 11.4.3).

+0

Очень приятно, лучше, чем мой ответ! – Motti

+2

@Motti: Мне очень нравится ваш ответ - я тоже это сделал. В зависимости от его требований это могло бы быть гораздо лучшим решением. Bjarnes немного сложнее, если все, что вы действительно хотите сделать, это остановить кого-то, использующего их тип вместо вашего. Я думаю, что важно рассмотреть, почему вы пытаетесь предотвратить деривацию, и действительно ли она достигает чего-то полезного. –

+0

Почему базовый класс виртуальный в этом решении? – SebastianK

2

Ну, у меня была аналогичная проблема. Это опубликовано here на SO. Проблема была другая; т. е. разрешать только эти классы, которые вы разрешаете. Проверьте, разрешает ли она вашу проблему.

Это делается по адресу время компиляции.

4

Посмотрите хорошо here.
Это действительно здорово, но это взломать.
Удивите себя, почему stdlib не делает этого со своими собственными контейнерами.

+2

Он делает - он использует второй из трех подходов. –

+0

У меня на самом деле был класс, который унаследовал карту один раз. Это не дало мне никаких ошибок. –

+0

ах жди ... никогда не думай. Я не помнил, что второй был LOL. –

0

Это решение не работает, но я оставляю его как пример того, что не делать.


Я не использовал C++ на некоторое время теперь, но, насколько я помню, вы получите то, что вы хотите, делая деструктор частным.

UPDATE:

В Visual Studio 2005 вы получите либо предупреждение или сообщение об ошибке. Проверьте следующий код:

class A 
{ 
public: 
    A(){} 
private: 
    ~A(){} 
}; 

class B : A 
{ 
}; 

Теперь

B б;

выдаст ошибку "Ошибка C2248: 'A :: ~ A': не может получить доступ к закрытому члену, объявленный в классе 'А'"

в то время как

B *b = new B(); 

произведет предупреждение «предупреждение C4624: «B»: деструктор не может быть сгенерирован, потому что деструктор базового класса недоступен ».

Похоже на половину solutiom, НО, как orsogufo указал, делая это делает класс A непригодным. Оставляя ответы

+1

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

+0

Черт, мне нужно обновить мой C++. Спасибо за это! – ya23

+0

приветствую :) спасибо за обновление сообщения. Во всяком случае, я имел в виду, что вы не можете создать экземпляр объекта типа A, а не только типа B. –

1

Я бы вообще этого добиться следующим образом:

// This class is *not* suitable for use as a base class 

Комментарий идет в заголовке и/или в документации. Если клиенты вашего класса не следуют инструкциям пакета, то в C++ они могут ожидать неопределенного поведения. Вывод без разрешения - это лишь частный случай. Вместо этого они должны использовать композицию.

Btw, это немного вводит в заблуждение: «базовый класс должен иметь деструктор, открытый и виртуальный или защищенный и не виртуальный».

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

Смежные вопросы