Ссылка на задачу назначения абстрактного класса - PullRequest
2 голосов
/ 08 сентября 2011

Относительно вопроса " ссылка на абстрактный класс " я написал следующий пример:

#include <iostream>
#include <vector>

class data
{
  public:
    virtual int get_value() = 0;
};

class data_impl : public data
{
  public:
    data_impl( int value_ ) : value( value_ ) {}
    virtual int get_value() { return value; }
  private:
    int value;
};

class database
{
  public:
    data& get( int index ) { return *v[index]; }
    void  add( data* d ) { v.push_back( d ); }
  private:
    std::vector< data* > v;
};

int main()
{
    data_impl d1( 3 );
    data_impl d2( 7 );

    database db;
    db.add( &d1 );
    db.add( &d2 );

    data& d = db.get( 0 );
    std::cout << d.get_value() << std::endl;
    d = db.get( 1 );
    std::cout << d.get_value() << std::endl;

    data& d_ = db.get( 1 );
    std::cout << d_.get_value() << std::endl;
    d_ = db.get( 0 );
    std::cout << d_.get_value() << std::endl;

    return 0;
}

К моему удивлению, пример печатает:

3
3
7
7

и похоже, что справочное задание работает не так, как я ожидал.Я бы ожидал:

3
7
7
3

Не могли бы вы указать, в чем моя ошибка?

Спасибо!

Ответы [ 6 ]

4 голосов
/ 08 сентября 2011

В

data& d = db.get( 0 );
std::cout << d.get_value() << std::endl;
d = db.get( 1 );
std::cout << d.get_value() << std::endl;

первый оператор является справочной инициализацией, в то время как третий оператор является срезающим присваиванием .

Вы не можете переустановить ссылку.

Приветствия & hth.,

2 голосов
/ 08 сентября 2011

Я собираюсь немного упростить пример:

data_impl a(3), b(7);
data &ra(a);
data &rb(b);
std::cout << ra.get_value() << std::endl;
ra = rb;                                  // [1]
std::cout << ra.get_value() << std::endl;

Теперь с этим упрощенным кодом легче рассуждать о программе.Вы получаете ссылку ra на подобъект a, но ссылка имеет тип data, а не data_impl.Аналогично с rb и b.В строке, помеченной [1], вы выполняете присваивание от rb до ra, статический тип двух аргументов выражения - data, и это означает, что независимо от того, к какому реальному объекту они относятсяв том, что эта конкретная строка назначает только подобъект data.Это называется нарезка .

То есть [1] устанавливает подобъект data для a таким же, как подобъект data для b.Поскольку data не содержит никаких фактических данных, в результате a остается неизменным.

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

Затем вы можете вручную попробовать агрегат operator= в качестве виртуальной функции и проверить, что вы можете получить ожидаемый результат, нореализация будет немного более запутанной, поскольку в C ++ нет ковариантных аргументов для функций-членов, что означает, что подпись operator= на всех уровнях будет принимать (const) ссылку на data, и вы будете вынужденыdowncast (убедитесь, что приведение выполнено успешно), а затем выполните назначение ...

2 голосов
/ 08 сентября 2011

Причина в том, что ссылки не могут быть переназначены.

Вы присвоили ссылку d первому элементу в db:

data& d = db.get( 0 );

Затем позже вы попыталисьпереназначить его:

d = db.get( 1 );

Однако это не меняет саму ссылку, вместо этого меняет значение, на которое указывает ссылка, на .

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

Фактически вы дважды печатаете первый элемент в db, а затем второй элемент снова дважды.

1 голос
/ 08 сентября 2011

Проблема может быть замечена здесь:

data& d = /**/;

d = /**/;        <---

Здесь статический тип d равен data&, что означает, что он фактически является синтаксическим сахаром для:

d.operator=(/**/);

Поскольку data::operator= не является виртуальным, то оно называется. Нет отправки в производный класс. А поскольку data является чистым интерфейсом, он не имеет никаких атрибутов, поэтому код не имеет смысла.


Проблема в том, что здесь нет реального решения:

  • Создание operator= виртуального является беспорядком, потому что параметр должен быть data const& в data и не может быть впоследствии изменен в последующих производных классах, что означает потерю безопасности типа
  • Использование виртуальной пары setValue / getValue будет работать только в том случае, если производные классы не содержат больше данных, чем они представляют через этот интерфейс, что является необоснованным предположением

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

Есть два способа сделать это:

  • Сделать всю иерархию не подлежащей копированию (либо наследовать от boost::noncopyable, либо delete оператор копирования и оператор назначения копирования и т. Д ...)

  • Сделать текущий класс недоступным для копирования, но разрешить автоматическое копирование производных классов, создав конструктор копирования и оператор присваивания protected.

Как правило, для нефинального класса является ошибкой иметь открытый конструктор копирования и оператор присваивания (интересно, если какой-либо компилятор это диагностирует).

0 голосов
/ 08 сентября 2011

Вы не можете отделить ссылку от референта. введите описание ссылки здесь

В отличие от указателя, когда ссылка привязана к объекту, она не может быть «переустановлена» к другому объекту. Сама ссылка не является объектом (она не имеет идентификатора; взятие адреса ссылки дает вам адрес референта; помните: ссылка является его референтом).

Самое странное, что компилятор не предупреждает об этом.

Я пытаюсь с gcc -Wall -pedantic

0 голосов
/ 08 сентября 2011

Следующие строки могут не соответствовать вашим ожиданиям: данные & d = db.get (0); std :: cout << d.get_value () << std :: endl; d = db.get (1); </p>

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

...