2014-01-27 3 views
3

Позвольте мне начать с того, что я понимаю, как работают виртуальные методы (полиморфизм, поздняя привязка, vtables).C++ следует использовать виртуальные методы?

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

Контекст:

Я создаю библиотеку. В этой библиотеке у меня есть класс CallStack, который захватывает стек вызовов и затем предлагает векторный доступ к захваченным кадрам стека. Захват выполняется методом protectedCaptureStack. Этот метод может быть переопределен в производном классе, если пользователи библиотеки хотят реализовать другой способ захвата стека. Чтобы быть ясным, обсуждение метода virtual применимо только к некоторым методам, которые, как мне известно, могут быть переопределены в производном классе (в данном случае CaptureStack и destructor), а не ко всем методам класса.

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

И я не могу придумать случай, когда кто-то захочет использовать CallStack в качестве указателя или ссылки для реализации полиморфизма. Если кто-то хочет получить CallStack и переопределить CaptureStack Я думаю, что просто использовать объект производного класса будет достаточно.

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


Пример, как CallStack может быть использован за пределами моей библиотеки:

if (error) { 
    CallStack call_stack; // the constructor calls CaptureStack 
    for (const auto &stack_frame : call_stack) { 
    cout << stack_frame << endl; 
    } 
} 

производного класса, который переопределяет CaptureStack может быть использование в той же манере, не нуждаясь полиморфизм:

if (error) { 
    // since this is not a CallStack pointer/reference, virtual would not be needed. 
    DerivedCallStack d_call_stack; 
    for (const auto &stack_frame : d_call_stack) { 
    cout << stack_frame << endl; 
    } 
} 
+1

Виртуальные функции являются дорогостоящими (с точки зрения производительности), но вы не всегда можете делать что-то без них. С другой стороны, C++ - это не только язык OOP, но и множество других функций/инструментов для выполнения такого рода вещей (параметрический полиморфизм через шаблоны, разработки на основе политик и т. Д.). Дело в том, что: ** C++ не ограничивается только полиморфизмом времени выполнения, поскольку другие языки OOP высокого уровня, такие как Java. ** Чтобы иметь полиморфное поведение, вам не нужно впадать в виртуальные функции. – Manu343726

+0

Общий намек: если вы не используете специальные шаблоны дизайна, вам, возможно, действительно не нужны виртуальные или чистые виртуальные методы. Framework designera также может использовать их. Я для себя действительно использую только виртуальные или чистые виртуальные методы, когда я проектирую архитектуру программного обеспечения, которая извлекает выгоду из переопределяющих супер методов. В простой разработке приложений я избегаю этого. Надеюсь это немного поможет. – icbytes

+0

@icbytes даже в больших программных архитектурах, виртуальные функции не нужны. Проекты на основе политик/компонентов, а не большие иерархии, основанные на booooom-объектах OOP, возможны, используются и не полагаются на виртуальные методы. – Manu343726

ответ

1

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

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

Или, как еще один пример, представьте, что кто-то извлекает класс из вашей реализации, переопределяет функцию и передает этот объект в качестве родительского класса для одной из ваших собственных реализованных функций/классов. Вы предпочли бы, чтобы ваша первоначальная реализация функции или вы хотите, чтобы вы использовали свою собственную переопределенную реализацию? Если это так, то вы должны пойти за virtual, если нет.

0

Вы должны почти наверняка объявить метод как virtual.

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

Рассмотрим следующий пример:

class Parent 
{ 
public: 
    void callFoo() 
    { 
     foo(); 
    } 

    void foo() 
    { 
     std::cout << "Parent::foo()" << std::endl; 
    } 
}; 

class Child : public Parent 
{ 
public: 
    void foo() 
    { 
     std::cout << "Child::foo()" << std::endl; 
    } 
}; 

int main() 
{ 
    Child obj; 
    obj.callFoo(); 
    return 0; 
} 

Клиент-код с помощью класса только когда-либо, используя полученный объект (не базовый класс указатель и т.д.). Тем не менее, это базовая версия класса foo(), которая фактически вызывается. Единственный способ разрешить это - сделать foo() виртуальным.

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

1

Мне непонятно, где вызывается CallStack. Из ваших примеров видно, что вы используете шаблонный шаблон , в котором базовые функции реализованы в базовом классе , но настроены с помощью виртуальных функций (обычно частных, незащищенных), которые предоставляются производный класс . В этом случае (как указывает Питер Блумфилд) функции должны быть виртуальными, так как они будут вызываться от внутри функции-члена базового класса; таким образом, со статическим типа CallStack. Однако: если я правильно понял ваши примеры , вызов будет CallStack в конструкторе. Это не будет работать, так как при строительстве CallStack, то динамического типа объекта CallStack, а не DerivedCallStack и вызовы виртуальных функций будут решать CallStack.

В таком случае для описанных вариантов использования может быть более подходящим решение с использованием шаблонов . Или даже ... Название класса ясно. Я не могу придумать какой-либо разумный случай, когда разные экземпляры должны иметь разные способы захвата стека вызовов в одной программе. Что указывает на то, что время соединения может быть подходящим. (Я использую идиомы брандмауэра компиляции и разрешение по времени ссылке в моей StackTrace класса.)

1

Мой вопрос, должен ли я сделать свой виртуальный метод. Я проиллюстрирую свою дилемму по конкретному делу, но любые общие рекомендации также будут приветствоваться.

Некоторые рекомендации:

  • , если вы не уверены, вы не должны это делать. Многие люди скажут вам, что ваш код должен быть легко расширяемым (и, как таковой, виртуальным), но на практике самый расширяемый код никогда не расширяется, если вы не создадите библиотеку, которая будет использоваться в значительной степени (см. Принцип YAGNI).

  • Вы можете использовать инкапсуляцию вместо наследования и тип полиморфизма (шаблоны) в качестве альтернативы иерархиям классов во многих случаях (например, std :: string и std :: wstring не являются двумя конкретными реализациями базового строкового класса и они не наследуются вообще).

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

2

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

Это C++. Одна вещь, которую люди часто ошибаются, когда приходит на C++ с другого языка, использует виртуальные методы в конструкторах. Это никогда не работает так, как планировалось.

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

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

Чтобы сделать его работу вы должны принять вызов из конструктора и использовать что-то вроде:

CallStack *stack = new DerivedStack; 
stack.CaptureStack(); 

Ни один из ваших примеров кода показывают хороший повод, чтобы сделать CaptureStack виртуальной.

+0

Следуя за заметками о конструкторах и виртуальных методах, я провел некоторое тестирование: базовый конструктор вызывает метод. Я выгляжу так: если в производном конструкторе вызывается метод, производный метод будет вызываться независимо от 'virtualness' базового метода. Если производный конструктор не вызывает метод, базовый метод будет вызываться (в базовом конструкторе) независимо от «виртуальности» метода. Правильны ли мои наблюдения? – bolov

+0

Для конкретного примера, когда метод вызывается из конструктора, ваш ответ находится на месте, спасибо. Тем не менее, я буду принимать еще один ответ, который дает более общие рекомендации. – bolov

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

  • Нет связанных вопросов^_^