Классы, конструктор и члены класса указателя - PullRequest
0 голосов
/ 02 апреля 2010

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

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 сделает все остальное, очистит и т. Д.

Ответы [ 5 ]

1 голос
/ 02 апреля 2010

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

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

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

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

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

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

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

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

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

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

1 голос
/ 02 апреля 2010

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

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;
};

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

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

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

1 голос
/ 02 апреля 2010

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

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

1 голос
/ 02 апреля 2010

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

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

0 голосов
/ 02 апреля 2010

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

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

Вы также можете рассмотреть

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

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

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