Подтвердить что ты не робот

Полиморфные ссылки на С++

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

Чтобы уточнить, см. следующий минимальный пример:

class A;

class B {
  public:
    A& a; ///////////////// <- #1
    B();
    void doStuff();
};

class A {
  public:
    virtual void doSmth() = 0;
};
void B::doStuff() {
  a.doSmth();
}

class A1 : public A {
  public:
    void doSmth() {
    }
};

B::B() : a(
    *        ////////////// <- #2
      (new A1)  /////////// <- #3
     ) {
}

Это компилируется и работает, но в качестве наиболее важного пункта здесь следует, что a в строке #1 является ссылкой, поэтому для того, чтобы иметь возможность использовать его полиморфно (это фактическое слово?), как показано в строке #3 Мне нужно "преобразовать указатель в ссылку", разыменовывая его.

Это кажется мне немного странным, и мне было интересно, есть ли лучший (в смысле чище) способ. Это только я?

Обоснование

Было бы здорово, если бы мне вообще не понадобился new, но при объявлении (!) B я не знаю, как создать экземпляр A1 (!), поскольку a is передняя декларация - A1 реализована в том же компиляционном блоке, что и B. Тем не менее, существует ли реальная потребность в распределении динамической памяти в этом случае? Как вы это сделаете?

Извините за слегка двоякий вопрос.

Изменить

Примечание: B огромен (и я не могу сделать его класс шаблона), и выйдет за пределы области точно, когда программа завершится - a будет маленькой и сделает два больших модуля разговаривать друг с другом, он понадобится, если существует экземпляр B (есть только один).

Изменить 2

Я просто понял, что, поскольку оба a и B являются эффективными синглтонами, я могу просто создать экземпляр static A1 в модуле компиляции B, избегая динамического распределения памяти (даже если было два B, они могли легко использовать один и тот же экземпляр a). Справедливости ради, я не публиковал это как ответ, но согласен с ответом, который побудил меня придумать это решение.

4b9b3361

Ответ 1

Нет ничего странного. Полиморфизмы работают как для указателей, так и для ссылок:

struct Base { };
struct Derived : Base;

void foo(Base &);

int main() {
  Derived x;
  foo(x);    // fine
}

Объедините это с другой проблемой, а именно создайте ссылку на динамический объект:

T * pt = new T;
T & rt = *pt;

T & x = *new T;  // same effect

Обратите внимание, что это вообще очень плохой стиль для отслеживания динамического объекта только по ссылке, потому что единственный способ удалить его - через delete &x;, и очень сложно увидеть, что x нуждается в очистке.

Для вашего дизайна есть две альтернативы: 1) сделать a объект-член в B или 2) сделать a a shared_ptr<A> или unique_ptr<A> и изменить инициализатор на a(new A1). Все зависит от того, действительно ли вам нужно полиморфное поведение, т.е. Если у вас есть другие конструкторы для B, которые присваивают другому производному классу a, отличному от A1.

Ответ 2

Тем не менее, существует ли реальная потребность в распределении динамической памяти в этом случай?

Нет. Сначала определите A1, а затем сделайте его нормальным членом B.

Полиморфизм отлично работает с рекомендациями и указателями.

Ответ 3

Это действительно немного странно. Если вам нужна переменная-член типа A1 (а не ссылка), почему бы не просто перестроить свой код, чтобы определение A1 появилось перед определением B?

Ответ 4

Erm, этого недостаточно?

#include <iostream>

struct A;

struct B
{
  B(A& a);

  void foo();

  A& _a;
};

struct A
{
  virtual void foo() =0;
};

struct A1 : public A
{
  virtual void foo() { std::cout << "A1::foo" << std::endl; }
};

B::B(A& a) : _a(a) {}
void B::foo() { _a.foo(); }


int main(void)
{ 
  A1 a;  // instance of A1
  B b(a); // construct B with it

  b.foo();
}

Ответ 5

Тем не менее, существует ли реальная потребность в распределении динамической памяти в этом случае?

Либо распределение динамической памяти, либо вводя ссылку в B ctor.

Ответ 6

Невозможно представить, почему ссылки могут работать полиморфно, как указатели (не говоря уже о том, что ссылки часто реализуются как указатели). Вот краткий пример:

class Base { 
public:
    virtual void something() { }
};

class Derived : public Base {
public:
    void something() { }
};

Base& foo() {
    static Derived d;
    return d;
}

foo().something(); // calls Derived something

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

Ответ 7

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

struct A
{
  int b;
  virtual void print();
  A(int val):b(val) {}
};

struct A_child:public A
{
  A_child(int val):A(val) {}
  void print();
};

void A:print()
{
cout<<"parent\n";
}

void A_child:print()
{
cout<<"child\n";
}

struct test_ref
{
A *& ref;
test_ref(A * ptr) : ref(ptr)
}

int main()
{

  test_ref parent(new A(12));
  parent.ref->print();

  test_ref child(new A_child(15));
  child.ref->print();
} 

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

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