Получение моих представлений об указателях и ссылках прямо - PullRequest
5 голосов
/ 31 августа 2010

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

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

Заранее спасибо, и здесьмы идем!

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

Пример:

class A{
public:
    A() : x(0) {};
    int x;
    virtual void doStuff() {
        cout << x <<endl;
    }
};

class B : public A{
public:
    B() : y(1) {};
    int y;
    virtual void doStuff() {
        cout << y <<endl;
    }
};

Если бы я преобразовал объект b типа B в A, что бы произошловнутренне виртуальная таблица b будет отброшена и заменена соответствующей виртуальной таблицей типа A, и будет вызван деструктор y, поскольку на нее больше нет ссылки.Аналогично, doStuff в b должен указывать на адрес функции A :: doStuff вместо B :: doStuff.Однако адрес, указывающий на x, останется прежним.

2.Это означает, что единственный способ воспользоваться полиморфизмом - это использовать указатели и ссылки.

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

Пример:

void doALotOfStuff(A a1, A a2) {
    a1.doStuff();
    a2.doStuff();
}

int main(){
    A a;
    B b;
    doALotOfStuff(a,b);
    return 0;
}

выведет

0
0  

потому что компилятор сгенерирует код для преобразования b в A.

3.Кроме того, единственный способ воспользоваться преимуществами этого вида полиморфизма с массивами и контейнерами STL состоит в том, чтобы использовать указатели, поскольку ссылки не могут быть сохранены

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

Извините, если это был TL; DR,но это было своего рода беспокойство, когда я занимался дизайном классов для своего проекта, и я понял, что не могу просто избежать использования указателей или ссылок из-за библиотечных интерфейсов и проблем полиморфизма.

Ответы [ 5 ]

3 голосов
/ 31 августа 2010

Хотя у вас есть несколько хороших идей в пункте № 1, на самом деле это не так. Преобразование не выполняется на месте, оно создается созданием нового объекта, который копирует члены, о которых он знает, из источника преобразования (то есть членов базового класса, если у вас нет действительно странных предварительных объявлений в базовом классе). ). Экземпляр производного объекта никак не изменяется. Конечно, копия имеет адрес памяти, отличный от исходного объекта. Передача по значению ВСЕГДА предполагает копирование.

3 голосов
/ 31 августа 2010
  1. Когда вы говорите «преобразовать объект b типа B в A», вы можете иметь в виду две разные вещи:

    • создать новый объект типа A, который скопирован из b или
    • сделать указатель типа A* или ссылку типа A&, которая указывает или ссылается на b.

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

    (За исключением редких случаев, когда оператор преобразования или участвующий конструктор принимает неконстантную ссылку на B и изменяет аргумент. Однако, даже в этом случае, это не замена vtable, как вы упомянули.)

    Как только объект создан, его тип не изменяется в течение всего времени его существования (до тех пор, пока он не будет уничтожен). Это означает, что упомянутая вами замена vtable никогда не произойдет.

  2. Да, полиморфизм в C ++ достигается с помощью указателей или ссылок.

    Обратите внимание, что в вашем примере a1 и a2 скопированы из a и b, и преобразование никак не изменило a или b.

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

0 голосов
/ 31 августа 2010

Ваша настоящая путаница связана с утверждением 1. Объекты никогда не преобразуются в C ++ - объект типа всегда является объектом этого типа (за исключением некоторой незначительной закулисности при создании базового класса объекта, который мы не будем говорить) Вместо этого VALUES преобразуются, и когда вы преобразовываете VALUE из одного типа объекта в другой, на самом деле происходит то, что вы создаете НОВЫЙ объект с данными из старого объекта.

Так, в вашем примере для 2, когда вы вызываете doALotOfStuff, компилятор вызовет конструктор A::A(const B &), чтобы создать новый A для передачи в качестве второго аргумента a2. ТАКЖЕ вызовет конструктор A::A(const A &) для создания нового A для a1, поэтому a1 и a2 - это совершенно разные объекты, чем a и b.

0 голосов
/ 31 августа 2010

Вы смешиваете передачу по значению и передачу по ссылке.

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

Когда вы передаете значение по значению и передаете производный тип, может произойти срезание объекта: http://en.wikipedia.org/wiki/Object_slicing

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

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

Из-за всего этого единственный способ сделать полиморфизм (действовать на базовом интерфейсе, но с производной реализацией) - это через указатели или ссылки.

И да, контейнеры STL не могут работать со ссылками.

Вы можете использовать умные указатели из Boost , чтобы обойти некоторые ограничения ссылок и обойти некоторые изобострение "тупых" указателей:)

0 голосов
/ 31 августа 2010

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

В пункте 1 вы говорите о «преобразовании» из типа B в A. Это немного запутано, так как более точно сказать, что новыйОбъект будет создан с соответствующими компонентами.Смотрите следующий пункт о нарезке.

В пункте 2 вы демонстрируете принцип нарезки .Здесь производный класс удаляет производную часть и остается только базовая.

Следует отметить, что вы должны использовать в своем контейнере STL boost :: shared_ptr или boost :: unique_ptr, потому что в противном случае памятьуправление становится опасной головной болью.

Если вы не слышали о повышении, вы можете проверить его здесь это чрезвычайно ценный ресурс.

...