Вопрос по methods, virtual, static, c++ – Как применить статический член в производных классах?

5

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

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

Моя проблема возникает в следующем: Я хочу, чтобы каждый производный экземпляр мог идентифицировать свой тип, скажем, черезstd::string type() член метод. Поскольку все экземпляры одного и того же класса будут возвращать один и тот же тип, имеет смысл сделатьtype() static метод. Как я тоже хочу каждыйPrimitive Подкласс для реализации этого метода, я также хотел бы сделать его чисто виртуальной функцией, какintersect() выше.

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

Может ли кто-нибудь помочь мне с вышеизложенным?

Я думал о звонке через инстанс, например [stackoverflow.com/questions/325555/… или даже черезthis указатель, например [publib.boulder.ibm.com/infocenter/lnxpcomp/v8v101/… wsaleem
+1 для того, чтобы заявить, что является реальной проблемой. Luchian Grigore
Как бы вы назвали виртуальную статику? John Dibling
Если вам нужен полиморфизм, зачем вам статический тип? Используйте указатели базового класса в вашем интерфейсе и дайте ему динамически определять тип во время выполнения. В этом весь смысл виртуальных методов. AJG85
Попробуйте изменить заголовок, чтобы отразить тот факт, что intersect () имеет отношение кpairs детских классов. Вопрос становится проще / другим, если вам нужны методы, относящиеся только к их собственному подклассу. einpoklum

Ваш Ответ

4   ответа
2

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

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

4

что у вас нет только 2 подклассов, поэтому давайте обобщим это.

Первое, что приходит на ум, этоcode duplication, extensibility and closeness. Давайте подробно остановимся на них:

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

Посколькуintersect операцияcommutativeкод для пересеченияA а такжеB должен быть в том же месте, что и код для пересеченияB а такжеAпоэтому о логике внутри самих классов не может быть и речи.

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

Я полагаю, что это ваша текущая структура (или похожая, возможно, возвращаемый тип дляintersect, но пока не важно)

struct Primitive
{
    virtual void intersect(Primitive* other) = 0;
};
struct Sphere : Primitive
{
    virtual void intersect(Primitive* other)
};
struct Plane : Primitive
{
    virtual void intersect(Primitive* other);
};

Мы уже решили, что нам не нужна логика пересечения внутриPlane или жеSphereпоэтому мы создаем новыйclass:

struct Intersect
{
    static void intersect(const Sphere&, const Plane&);
    //this again with the parameters inversed, which just takes this
    static void intersect(const Sphere&, const Sphere&);
    static void intersect(const Plane&, const Plane&);
};

Это класс, в который вы будете добавлять новые функции и новую логику. Например, если вы решили добавитьLine класс, вы просто добавляете методыintersec(const Line&,...).

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

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

struct IntersectBehavior
{  
    Primitive* object;
    virtual void doIntersect(Primitive* other) = 0;
};
struct SphereIntersectBehavior : IntersectBehavior
{
    virtual void doIntersect(Primitive* other)
    {
        //we already know object is a Sphere
        Sphere& obj1 = (Sphere&)*object;
        if ( dynamic_cast<Sphere*>(other) )
            return Intersect::intersect(obj1, (Sphere&) *other);
        if ( dynamic_cast<Plane*>(other) )
            return Intersect::intersect(obj1, (Plane&) *other);

        //finally, if no conditions were met, call intersect on other
        return other->intersect(object);
    }
};

И в наших оригинальных способах мы имеем:

struct Sphere : Primitive
{
    virtual void intersect(Primitive* other)
    {
        SphereIntersectBehavior intersectBehavior;
        return intersectBehavior.doIntersect(other);
    }
};

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

struct Sphere : Primitive
{
    virtual void intersect(Primitive* other)
    {
        IntersectBehavior*  intersectBehavior = BehaviorFactory::getBehavior(this);
        return intersectBehavior.doIntersect(other);
    }
};

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

If you follow this design

no need to modify existing code when adding new classes have the implementations in a single place extend only IntersectBehavior for each new type provide implementations in the Intersect class for new types

Бьюсь об заклад, это может быть усовершенствовано еще дальше.

Ваша идентификация типа в конечном итоге сводится кdynamic_castИспользование которого, как я уже понял, не поощряется. Есть мнения? wsaleem
Спасибо за подробный ответ. Я предполагаю, что моя экономия выражения в оригинальном посте привела вас в заблуждение.intersect() принимаетconst Ray& в качестве аргумента, а не (указатель на) другойPrimitive экземпляров. Кажется разумным, что каждыйPrimitive класс знает, как он пересекается сRay, перемещениеintersect снаружи потребуется обычайIntersect класс для доступаPrimitive внутренние органы, которые нарушают инкапсуляцию. wsaleem
@ wsaleem хорошо, у них нетid член. Ноdynamic_cast уже существует, зачем изобретать велосипед?
"You use a static method when you don't need an instance ... so you do need an instance." Спасибо, это очень верно. Я не думал об этом."All method bodies are shared by all objects anyway, so there's no need to worry about duplication." Еще один хороший момент. wsaleem
1

it makes sense to make type() a static method.

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

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

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

"You use a static method when you don't need an instance ... so you do need an instance." Спасибо, это очень верно. Я не думал об этом."All method bodies are shared by all objects anyway, so there's no need to worry about duplication." Еще один хороший момент. wsaleem
И вы, и @ ajg85 находите мой дизайн любопытным. Позвольте мне уточнить, и вы можете предложить лучшую альтернативу. Я хочу позвонитьPrimitive::emitXml() метод черезPrimitive* объект. Излучаемый XML содержит, например,<Primitive type='Triangle'> ... </Primitive>в зависимости от типаPrimitive объект, на который указывает указатель. мне нужноtype() функция для заполнения значенияtype атрибут вemitXml() функция. wsaleem
@wsaleem, это действительно законный вариант использования. Большую часть времени намеревается использовать его вif операторы, чтобы получить различное поведение в зависимости от типа объекта, и это - запах кода, на который я ссылался.
0

вызывающий статический (или возвращающий статическую строку), соответствующим образом реализованный в каждом производном классе.

#include <iostream>
#include <string>

struct IShape {
  virtual const std::string& type() const =0;
};

struct Square : virtual public IShape {
  virtual const std::string& type() const { return type_; }
  static std::string type_;
};
std::string Square::type_("square");

int main() {

  IShape* shape = new Square;
  std::cout << shape->type() << "\n";

}

Обратите внимание, что вам придется реализоватьtype() В любом случае, метод для каждого подкласса, поэтому лучшее, что вы можете сделать, это чтобы строка была статической. Тем не менее, вы можете рассмотреть возможность использованияenum вместо строки избегайте ненужных сравнений строк в вашем коде.

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

@juanchopanza вы можете увидеть мой ответ.
К сожалению, вы все равно не можете отказаться от виртуального метода для каждого подкласса. Я добавил еще немного информации и комментариев, хотя. Это сложная проблема ...
@LuchianGrigore Я имел ввиду в контексте наличия метода, который возвращает статический идентификатор класса, а не в контексте разумного замысла :-)
Спасибо, мне нравится идея возврата статической строки через виртуальный метод. Но тогда я быstatic std::string myType объявление в каждом подклассе. Это похоже на дублирование кода. Могу ли я не использовать наследование, чтобы избежать его? wsaleem
@juanchopanza Я сейчас думаю, как насчетreturn "square" вSquare::type() и угробилstatic std::string пример? Компилятор может даже оптимизировать для постоянной строки"square" каким-то образом. Есть мнения? wsaleem

Похожие вопросы