Возвращение вектора указателей - понимание - PullRequest
4 голосов
/ 01 декабря 2011

Я пытаюсь понять следующее (допустим, что MyStorageClass огромен):

class MyStorageClass
{
public:
  string x;
  string y;
  string z;
};

class storage
{
public:
  storage();
  ~storage()
  {
    vector<MyStorageClass *>::iterator it = myVec.begin(); it != myVec.end(); it++)
    {
      delete *it;
    }

  vector<MyStorageClass*> getItems()
  {
    for(int i = 0; i < 10; ++i)
    { 
      v.push_back(new MyStorageClass());
    }
    return v;
  }

private:
  vector<MyStorageClass*> v;

};



main()
{
  storage s;
  vector<MyStorageClass*> vm = s.getItems();
}

Насколько я понимаю, когда s возвращает вектор и присваивается vm, это делается как копия (по значению). Так что если s вышел из области видимости и назвал его деструктором, vm имеет свою собственную копию, и на его структуру это не влияет. Однако передача по значению неэффективна. Итак, если вы измените его, чтобы передать по ссылке:

vector<MyStorageClass*>& getItems()
{
  for(int i = 0; i < 10; ++i)
  { 
    v.push_back(new MyStorageClass());
  }
  return v;
}

Вы передаете ячейку памяти v (в классе Storage). Но вы все равно назначаете его копию с помощью оператора = вектору vm в классе Main. Итак, vm не зависит от v, и если Storage деструктор называется vm, то он не затронут.

Наконец, если getItems вернул ссылку и в основном у вас было следующее:

main()
{
  storage s;
  vector<MyStorageClass*> &vm = s.getItems();
}

Теперь vm содержит адрес v. Так что на него влияет деструктор Storage.

Вопрос: верно ли то, что я сказал выше?

Ответы [ 5 ]

7 голосов
/ 01 декабря 2011

Да, вы правильно поняли. Теперь, если вы хотите перейти на следующий уровень, начните с поиска причин, почему это плохо:

  1. Возвращение указателя или ссылки на внутреннее содержимое класса нарушает инкапсуляцию.
  2. Ссылка, которую вы получите, станет висячей ссылкой после уничтожения соответствующего s -объекта, что нарушит инвариант, что ссылки всегда действительны.
  3. Возвращая вектор указателей, вы оставляете вызывающему объекту вопрос о том, должен он удалять эти указатели или нет.

Лучшим решением было бы для storage выставить методы begin и end, которые возвращают соответствующие итераторы из s. Кроме того, вы можете использовать Visitor -pattern для алгоритмов, которые должны работать на s.

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

0 голосов
/ 02 декабря 2011

Насколько я понимаю, когда s возвращает вектор и присваивается vm, это делается как копия (по значению).Поэтому, если s вышел из области видимости и вызывает его как деструктор, vm имеет свою собственную копию, и на его структуру это не влияет.

За простыми указателями (T*) не следует std::vector конструктор копирования или оператор присваивания (хотя вы можете использовать класс интеллектуального указателя, который будет копировать).Поскольку то, на что указывают указатели («остроконечные»), не копируется, операция копирования является поверхностной копией.Хотя операции с s.v не влияют на vm (и наоборот), все, что влияет на острие, к которому обращается один, повлияет на другое, поскольку они одинаковы.

Если вы должны были сохранитьдолжным образом реализованный интеллектуальный указатель в s.v, а не MyStorageClass*, стандартные операции копирования std::vector могут создавать глубокие копии, так что содержимое s.v может быть изменено без какого-либо влияния на содержимое vm (и наоборот).Операции копирования указателя копирования будут использовать операции копирования указанного класса для дублирования указанного объекта.В результате на каждый указанный объект может указывать только один указатель копирования.

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

Однако передача по значению неэффективна.

Однако в некоторых случаях это правильнов частности, когда мутирование одного экземпляра контейнера не должно влиять на другой (упомянутый вами случай), в этом случае вы не можете уйти без копии.Правильные козыри эффективны.Вообще говоря, вы можете использовать идиому copy-swap , чтобы сократить неэффективность из-за создания временных.Насколько я знаю, большинство реализаций STL не используют copy-swap для std::vector. Копирование при записи , которое будет выполнять копирование только при изменении вектора, также может помочь.Потоки усложняют копирование при записи и могут привести к неэффективности.

Теперь vm содержит адрес v.

vm не содержит адреса.В то время как ссылки могут использовать скрытые указатели (они также могут и не иметь; они могут даже не требовать дополнительного хранения, согласно §8.3.2-3 C ++ 03), на уровне языка ссылки не являются указателями.Учтите, что вы можете иметь нулевые указатели, но не нулевые ссылки.Более точное утверждение состоит в том, что vm имеет псевдоним v (vm и v относятся к одному и тому же объекту), поэтому операции с v влияют на vm (и наоборот).

0 голосов
/ 01 декабря 2011

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

0 голосов
/ 01 декабря 2011

Даже если вектор копирует свои значения, значения в вашем случае являются указателями, а не объектами, на которые указывают.Так что «vm имеет свою собственную копию» не совсем верно: результирующий вектор в вашем первом фрагменте кода будет иметь копию указателей , но не объектов MyStorageClass, на которые они указывают;так что на самом деле во всех ваших трех примерах кода указатели, хранящиеся в vm, больше не будут действительными, если вызывается деструктор Storage!

Если вам нужно убедиться, что Storage isn 'В любом случае, если он был уничтожен до последнего доступа к одному из объектов MyStorageClass, то из представленных методов предпочтительным будет третий путь, поскольку векторные данные (то есть указатели) находятся в памяти только один раз.Вы должны рассмотреть возможность возврата вектора const, однако каждый вызывающий getItems может изменить вектор v класса Storage с помощью возвращенной ссылки.

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

Более того, использование указателей - там, где это не является абсолютно необходимым, - в больших проектах обычно не одобряется.Подумайте об использовании интеллектуальных указателей, таких как std::auto_ptr (не очень рекомендуется, хотя из-за странной семантики копирования) или более понятных boost::shared_ptr / std::tr1::shared_ptr.

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

0 голосов
/ 01 декабря 2011

Я думаю, что вы заявили, что это правда, но я немного запутался в цели.

vector<MyStorageClass*> &vm = s.getItems();

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...