2010-04-02 2 views
0

Я немного смущен ссылками на объекты. Пожалуйста, проверьте приведенные ниже примеры:Классы, классы конструктора и указателя

class ListHandler { 
public: 
    ListHandler(vector<int> &list); 
private: 
    vector<int> list; 
} 

ListHandler::ListHandler(vector<int> &list) { 
    this->list = list; 
} 

Из-за внутреннего определения

vector<int> list; 

, здесь я бы тратить право памяти? Таким образом, правильным будет:

class ListHandler { 
public: 
    ListHandler(vector<int>* list); 
private: 
    vector<int>* list; 
} 

ListHandler::ListHandler(vector<int>* list) { 
    this->list = list; 
} 

ListHandler::~ListHandler() { 
    delete list; 
} 

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

+1

Для чего это? Больше контекста даст вам лучшие ответы. – GManNickG

ответ

1

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

 
class ListHandler 
{ 
    public: 
     ListHandler(const std::vector<int>& list) : _list(list) {} 
     ~ListHandler(){} 
    private: 
     std::vector<int> _list; 
}; 

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

 
class ListHandler 
{ 
    public: 
     ListHandler(std::vector<int>& list) : _list(&list) {} 
     ~ListHandler(){} 
    private: 
     std::vector<int>* _list; 
}; 

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

 
class ListHandler 
{ 
    public: 
     ListHandler(std::vector<int>* list) : _list(list) {} 
     ListHandler(const ListHandler& o) : _list(new std::vector<int>(o._list)) {} 
     ~ListHandler(){ delete _list; _list=0; } 

     ListHandler& swap(ListHandler& o){ std::swap(_list,o._list); return *this; } 
     ListHandler& operator=(const ListHandler& o){ ListHandler cpy(o); return swap(cpy); } 
    private: 
     std::vector<int>* _list; 
}; 

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

 
class ListHandler 
{ 
    public: 
     ListHandler(const boost::shared_ptr< std::vector<int> >& list) : _list(list) {} 
     ~ListHandler(){} 
    private: 
     boost::shared_ptr< std::vector<int> > _list; 
}; 

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

+1

Остерегайтесь: в третьем примере (владелец объекта) вы не переопределили Копировать и Назначение, таким образом, у вас есть эффективное неопределенное поведение, ожидающее своего появления. Кроме того, нецелесообразно брать собственность без явного обьявления, если вы берете на себя ответственность за указатель, необходимо передать в свой интерфейс интеллектуальный указатель типа 'unique_ptr', чтобы вызывающий не мог случайно вызвать его со ссылкой на например, объект в стеке. –

+0

@Mathhieu, спасибо. Хороший улов. Я исправлю это. Одна из причин, по которой мне не нравится это решение, я сам. –

0

Нет единого «правильного пути». Однако ваш второй пример будет очень плохим, потому что ListHandler приобретает право собственности на вектор, когда он построен. Каждый new должен быть тесно связан с его delete, если это вообще возможно - серьезно, это очень высокий приоритет.

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

Вы могли бы также рассмотреть

ListHandler::ListHandler(vector<int> &list) { 
    this->list.swap(list); 
} 

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

+0

Swap меняет содержимое, что я не хочу делать. Я просто не хочу создавать 2 векторных объекта.Я думаю, в моем определении: vector список; создает один в классе. В конструкторе я передаю ему еще один вектор, но он уже создан. Поэтому я передаю ссылку, но это что-то вроде создания вектора, а затем моя переменная начинает указывать на другой вектор, который не является хорошим. – pocoa

+1

Я бы не стал рассматривать второй пример плохого стиля. Это не такой «очень высокий приоритет» для того, чтобы сблизить новый/удалить. Заводские функции отделяют их полностью. Второй пример - прекрасный пример передачи права собственности. – Stephen

+0

@pocoa: swap просто перемещает содержимое исходного списка в пункт назначения. Когда вы передаете вектор источника, он может быть в стеке. Вектор внутри «ListHandler» должен находиться где-то в другом месте. Следовательно, у вас должны быть два векторных объекта. Они очень легкие; вы делаете свою программу больше и медленнее, пытаясь свести их к минимуму с помощью «нового вектора». – Potatoswatter

1

Первый пример - не обязательно тратить память, просто сделав копию всего вектора в списке «this-> list = list»; строка (которая может быть то, что вы хотите, зависит от контекста). Это потому, что оператор = метод на векторе вызывается в той точке, которая для вектора делает полную копию самого себя и всего его содержимого.

Второй пример, безусловно, не делает копию вектора, просто присваивая адрес памяти. Хотя вызывающий объект ListHandler contructor лучше понимает, что ListHandler берет на себя управление указателем, поскольку он в конечном итоге освободит память в конце.

+0

Итак, вы имеете в виду, что второй справа? – pocoa

+2

Если вектор создан с помощью «нового», а затем владение этим вектором должно быть передано в ListHandler, который уничтожит его позже, и вы не захотите сделать копию вектора в процессе, затем второй вариант сделают это для вас. – cpalmer

+1

Но у вас должна быть причина для передачи права собственности, кроме попытки сохранить память, имея «меньше» объектов «вектор». – Potatoswatter

1

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

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

+0

Да, все, что я хочу сделать, это передать право собственности на ListHandler. Является ли второй хорошим для этой цели? – pocoa

+0

Да, ваш второй пример в порядке. – Stephen

1

Все зависит от того, что вы хотите и какие политики вы можете обеспечить. Нет ничего «неправильного» в вашем первом примере (хотя я бы избегал явно использовать this->, выбирая разные имена). Он делает копию вектора, и это может быть правильным. Это может быть самая безопасная вещь.

Но похоже, что вы хотели бы использовать один и тот же вектор. Если список может пережить любой ListHandler, вы можете использовать ссылку вместо указателя. Хитрость заключается в том, что переменная член ссылки должны быть инициализированы в списке инициализации в конструкторе, например, так:

class ListHandler 
{ 
public: 
    ListHandler(const vector<int> &list) 
    : list_m(list) 
    { 
    } 
private: 
    vector<int>& list_m; 
}; 

список инициализации бит после двоеточия, но до тела.

Однако это не эквивалентно вашему второму примеру, который использует указатель и вызывает delete в своем деструкторе. Это третий способ, в котором ListHandler принимает на себя право собственности на список. Но код приходит с опасностями, потому что, позвонив delete, он предполагает, что список был выделен new. Один из способов уточнения этой политики является использованием именования (например, префикс «принять»), который идентифицирует изменение собственности:

ListHandler::ListHandler(vector<int> *adoptList) 
: list_m(adoptList) 
{ 
} 

(Это то же самое, как ваши, для смены названия, за исключение, и . использование списка инициализации)

Итак, теперь мы видим три варианта:

  1. Копировать список.
  2. Содержите ссылку на список, который принадлежит кому-то другому.
  3. Предположим, что вы владеете списком, созданным кем-то с new.

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

Смежные вопросы