Переопределение статических переменных при создании подклассов

У меня есть класс, назовем его A, и в определении этого класса у меня есть следующее:

static QPainterPath *path;

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

Если я подклассифицирую его для создания класса F (например), я хочу, чтобы F использовал унаследованные процедуры рисования от A, но использовал статический (для всего класса) объект пути, объявленный в F. Я попытался использовать объявление выше в приватный раздел (и повторив его в производном классе F) и попытался использовать его в защищенном разделе, все без радости.

Я вроде как понимаю, почему это происходит:

void A::paint() {
    this->path...

ссылается на A::path вместо F::path, даже если объект относится к классу F.

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


person Community    schedule 27.02.2009    source источник


Ответы (8)


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

class Base {
private:
    static A *a;
public:
    A* GetA() {
        return a;
    }
};

class Derived: public Base {
private:
    static B *b;
public:
    A* GetA() {
        return b;
    }
};

Обратите внимание, что B здесь происходит от A. Потом:

void Derived::paint() {
    this->GetA() ...
}
person Joao da Silva    schedule 27.02.2009
comment
Действительно, спасибо, что заметили. Я также не упомянул, что статические переменные должны быть как-то инициализированы :) - person Joao da Silva; 27.02.2009
comment
Спасибо, это помогло мне решить проблему красиво. Я выбрал отдельные имена для статических членов и простой функции-получателя, но, поскольку цель состоит в том, чтобы избежать производных::paint() в каждом классе, я сделал getPath() виртуальным, что все исправило. Большое спасибо. - person ; 27.02.2009
comment
Вы говорите использовать виртуальный метод, но я нигде не вижу ключевого слова virtual в этом коде. Что делает метод виртуальным? - person abelenky; 20.01.2015
comment
@abelenky Метод действительно не виртуальный, хотя и переопределен. Следствием этого является то, что когда у вас есть объект B и указатель типа A*, который указывает на ваш B и вызывает метод, тогда вызывается версия A, а не версия B (когда вы делаете это с указателем типа B*, тогда он нормально работает). - person Jupiter; 08.05.2018

Возможно, вы сможете создать вариант в миксе или в любопытно повторяющемся шаблоне шаблона.

#include <stdio.h>

typedef const char QPainterPath;

class Base
{
public:
    virtual void paint() { printf( "test: %s\n", getPath() ); }
    virtual QPainterPath* getPath() = 0;
};

template <class TYPE>
class Holder : public Base
{
protected:
    static QPainterPath* path;
    virtual QPainterPath* getPath() { return path; }
};

class Data1 : public Holder<Data1>
{
};

class Data2 : public Holder<Data2>
{
};

template <> QPainterPath* Holder<Data1>::path = "Data1";
template <> QPainterPath* Holder<Data2>::path = "Data2";

int main( int argc, char* argv[] )
{
Base* data = new Data1;
data->paint();
delete data;

data = new Data2;
data->paint();
delete data;
}

Я только что запустил этот код в CodeBlocks и получил следующее:

test: Data1
test: Data2

Process returned 0 (0x0)   execution time : 0.029 s
Press any key to continue.
person David Allan Finch    schedule 27.02.2009
comment
Чтобы решить проблему спрашивающего, вам также потребуется переместить все подпрограммы рисования в этот класс-шаблон. Это может нарушить другие требования к дизайну (например, типичный сценарий наличия контейнера указателей на базу и вызова x-›do_something() для каждого — методы шаблона не могут быть виртуальными). - person j_random_hacker; 27.02.2009
comment
Да, это так, но теперь я понимаю, что это не шаблон CRTP: TYPE нигде не используется внутри шаблонного класса Holder. Держатель даже не должен быть шаблоном. То, что у вас есть, в основном такое же простое решение для виртуальных функций, предоставленное другими, но с двумя членами, перемещенными в промежуточный класс. - person j_random_hacker; 27.02.2009
comment
Я думаю, что мы должны согласиться не соглашаться здесь, поскольку это CRTP, и это лучшее решение, поскольку компоновщик сообщит вам, если вы забыли создать новый путь для нового типа. - person David Allan Finch; 27.02.2009
comment
Извините, я думаю, это CRTP, я просто привык видеть, что вместо этого используется полиморфизм времени компиляции. Да, компоновщик будет жаловаться, если вы забудете определение, и это хорошо. OTOH вам необходимо определить новый класс шаблона в стиле держателя для каждого класса, из которого можно получить производные. - person j_random_hacker; 27.02.2009
comment
Нет, если вы сделали этот шаблон ‹class TYPE,class BASE› владелец класса: public BASE :) - person David Allan Finch; 27.02.2009
comment
Истинный. Но вот моя следующая жалоба :) Я думаю, что проблема, от которой защищает этот подход (а именно, забывание добавить эти 2 члена в производный класс), примерно так же вероятна, как случайное получение непосредственно из Base, а не через шаблон Holder (который имеет тот же эффект). - person j_random_hacker; 28.02.2009
comment
+1 за то, что заставил меня больше думать об этом подходе и CRTP в целом :) - person j_random_hacker; 28.02.2009
comment
Поскольку Base::getPath() является абстрактным, вы получите ошибку компиляции, если будете получать данные непосредственно из Base. Большое спасибо за +1. - person David Allan Finch; 03.03.2009

Я не тестировал это, но представил виртуальную функцию:

struct Base {

    void paint() {
         APath * p = getPath();
         // do something with p
    }

    virtual APath * getPath() {
         return myPath;
    }

    static APath * myPath;
};

struct Derived : public Base  {

    APath * getPath() {
         return myPath;
    }
    static APath * myPath;
};

может быть то, что вы хотите. Обратите внимание, что вам все еще нужно где-то определить две статики:

APath * Base::myPath = 0;
APath * Derived::myPath = 0;
person Community    schedule 27.02.2009
comment
+1. Полно и ясно. (Я исправил пару опечаток.) ​​Хотя вы можете вызвать некоторую путаницу у программиста, назвав статическую переменную одинаково в обоих классах, имейте в виду (мне пришлось проверить, позволяет ли это C++!) - person j_random_hacker; 27.02.2009
comment
Это должен быть ответ - он более полный и работает из коробки. - person ivan-k; 08.01.2015

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

class A
{
    protected:
        virtual QPainterPath *path() = 0;

    private:
        static QPainterPath *static_path;  /* Lazy initalization? */
};

QPainterPath *A::path()
{
    return A::static_path;
}

class F : public A
{
    protected:
        virtual QPainterPath *path() = 0;

    private:
        static QPainterPath *F_static_path;  /* Lazy initalization? */
};

QPainterPath *A::path()
{
    return F::F_static_path;
}
person strager    schedule 27.02.2009
comment
Есть ли причина, по которой вы добавили = 0, чтобы сделать path() чисто виртуальным? Насколько мне известно, все, что нужно сделать, это отключить динамическую отправку для path(), т. е. ее нельзя вызвать, когда конкретный тип объекта неизвестен во время компиляции, и я не понимаю, почему это желательно. В противном случае ваше решение выглядит хорошо! - person j_random_hacker; 27.02.2009
comment
@j_random_hacker, насколько я знаю, это приводит к повторной реализации функции. Тело функции задано, поэтому ее можно вызвать. Может быть, я сам что-то упускаю (что маловероятно). - person strager; 27.02.2009
comment
@strager: Что ж, кажется, мы оба частично правы (и частично неправы... :) Согласно этой интересной странице: gotw.ca/gotw/031.htm, добавление =0 вызывает повторную реализацию в производном классе, однако это также предотвращает создание экземпляра текущего класса. - person j_random_hacker; 28.02.2009

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

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

Пример :

template <typename T>
struct Helper {
  static QPainterPath* path;
  static void routine();
}

// Define default values
template <typename T> QPainterPath* Helper<T>::path = some_default_value;
template <typename T> void Helper<T>::routine { do_somehing(); }

class Derived {};

// Define specialized values for Derived
QPainterPath* Helper<Dervied>::path = some_other_value;
void Helper<Dervied>::routine { do_somehing_else(); }

int main(int argc, char** argv) {
  QPainterPath* path = Helper<Derived>::path;
  Helper<Derived>::routine();
  return 0;
}

Плюсы:

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

Минусы:

  • никакой виртуализации, вам нужен точный тип для получения информации
person brainsandwich    schedule 12.07.2016

Вы не можете «переопределить» статические функции, не говоря уже о статических переменных-членах.

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

person Anton Gogolev    schedule 27.02.2009

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

class A
{
    public:
        A() :
            path(static_path)
        {
        }

    protected:
        A(QPainterPath *path)
            : path(path)
        {
        }

    private:
        QPainterPath *path;

        static QPainterPath *static_path;  /* Lazy initalization? */
};

class F : public A
{
    public:
        F() :
            A(F_static_path)
        {
        }

    private:
        static QPainterPath *F_static_path;  /* Lazy initalization? */
};
person strager    schedule 27.02.2009

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

Другой вариант - использовать функцию, чтобы убрать это, например. виртуальный QPainterPath* GetPath() { return A::path; } в A и QPainterPath* GetPath() { return F::path; } в Ф.

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

person jheriko    schedule 27.02.2009
comment
Я думаю, что это имеет значение, потому что вы хотите повторно использовать унаследованные процедуры рисования для другого указателя. Вы не можете сделать это, добавив A:: или F:: в подпрограммы рисования, поскольку они не знают, какой тип им нужен! Здесь необходимы виртуальные функции. - person j_random_hacker; 27.02.2009