Правильный способ переназначения указателей в C ++ - PullRequest
6 голосов
/ 16 апреля 2010

РЕДАКТИРОВАТЬ: я знаю, в этом случае, если бы это был реальный класс, мне было бы лучше не помещать строку в кучу. Тем не менее, это просто пример кода, чтобы убедиться, что я понимаю теорию. Фактический код будет красным черным деревом со всеми узлами, хранящимися в куче.

Я хочу убедиться, что у меня есть правильные основные идеи, прежде чем двигаться дальше (я работаю на Java / Python). Я искал в сети, но пока не нашел конкретного ответа на этот вопрос.

Когда вы переназначаете указатель на новый объект, нужно ли сначала вызывать delete для старого объекта, чтобы избежать утечки памяти? Моя интуиция говорит мне, что да, но я хочу получить конкретный ответ, прежде чем двигаться дальше.

Например, допустим, у вас есть класс, в котором хранится указатель на строку

class MyClass
{
private:
    std::string *str;

public:
MyClass (const std::string &_str)
{
    str=new std::string(_str);
}

void ChangeString(const std::string &_str)
{
    // I am wondering if this is correct?
    delete str;
    str = new std::string(_str)

    /*
     * or could you simply do it like:
     * str = _str;
     */ 
}
....

В методе ChangeString, что было бы правильно?

Я думаю, что зацикливаюсь на том, что если вы не используете новое ключевое слово для второго способа, оно все равно будет компилироваться и запускаться, как вы ожидали. Это просто перезаписывает данные, на которые указывает этот указатель? Или это делает что-то еще?

Любой совет будет очень ценным: D

Ответы [ 7 ]

12 голосов
/ 16 апреля 2010

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

void reset(const std::string& str)
{
    std::string* tmp = new std::string(str);
    delete m_str;
    m_str = tmp;
}

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

Этого также можно избежать, установив указатель в промежуточное значение NULL, но описанный выше способ все же лучше: если сбой сброса, объект сохранит свое первоначальное значение.


По вопросу в комментарии к коду.

*str = _str;

Это было бы правильно. Это обычное строковое присвоение.

str = &_str;

Это было бы назначение указателей и совершенно неправильно. Вы бы пропустили экземпляр строки, на который ранее указывал str. Хуже того, вполне вероятно, что строка, переданная функции, не выделяется с new, во-первых (вы не должны смешивать указатели на динамически размещаемые и автоматические объекты). Кроме того, вы можете хранить адрес строкового объекта, время жизни которого заканчивается вызовом функции (если ссылка на const связана с временной).

3 голосов
/ 16 апреля 2010

Почему вы думаете, что вам нужно хранить указатель на строку в вашем классе? Указатели на коллекции C ++, такие как string, на самом деле очень редко нужны. Ваш класс почти наверняка должен выглядеть так:

class MyClass
{
private:
    std::string str;

public:
MyClass (const std::string & astr) : str( astr )
{
}

void ChangeString(const std::string & astr)
{
    str = astr;
}
....
};
1 голос
/ 16 апреля 2010

Я просто напишу класс для вас.

class A
{
     Foo * foo;   // private by default
 public:
     A(Foo * foo_): foo(foo_) {}
     A(): foo(0) {}   // in case you need a no-arguments ("default") constructor
     A(const A &a):foo(new Foo(a.foo)) {}   // this is tricky; explanation below
     A& operator=(const &A a) { foo = new Foo(a.foo); return *this; }
     void setFoo(Foo * foo_) { delete foo; foo = foo_; }
     ~A() { delete foo; }
}

Для классов, содержащих такие ресурсы, необходим конструктор копирования, оператор присваивания и деструктор. Сложная часть конструктора копирования и оператора присваивания заключается в том, что вам нужно удалить каждый Foo точно один раз. Если инициализатор конструктора копирования сказал :foo(a.foo), то этот конкретный Foo будет удален один раз, когда инициализируемый объект был уничтожен, и один раз, когда был инициализирован объект из (a).

Класс, как я его написал, должен быть задокументирован как получающий владение указателем Foo, который он передает, потому что Foo * f = new Foo(); A a(f); delete f; также приведет к двойному удалению.

Еще один способ сделать это - использовать интеллектуальные указатели Boost (которые были ядром интеллектуальных указателей следующего стандарта) и иметь boost::shared_ptr<Foo> foo; вместо Foo * f; в определении класса. В этом случае конструктор копирования должен иметь значение A(const A &a):foo(a.foo) {}, поскольку интеллектуальный указатель позаботится об удалении Foo, когда все копии общего указателя, указывающего на него, будут уничтожены. (Есть проблемы, с которыми вы можете столкнуться и здесь, особенно если вы смешаете shared_ptr<> s с любой другой формой указателя, но если вы будете придерживаться shared_ptr<> повсюду, все будет в порядке.)

Примечание: я пишу это без запуска через компилятор. Я стремлюсь к точности и хорошему стилю (например, использование инициализаторов в конструкторах). Если кто-то обнаружит проблему, пожалуйста, прокомментируйте.

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

Общее правило в C ++ состоит в том, что для каждого объекта, созданного с помощью «new», должно быть «delete». Следить за тем, чтобы это всегда происходило в трудной части;) Современные программисты на C ++ избегают создания памяти в куче (то есть с «новым»), как чума, и вместо этого используют объекты стека. Действительно подумайте, нужно ли вам использовать «новый» в вашем коде. Это редко требуется.

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

#include <boost/shared_ptr.hpp>
...
boost::shared_ptr<MyClass> myPointer = boost::shared_ptr<MyClass>(new MyClass());

myPointer имеет почти ту же семантику языка, что и обычный указатель, но shared_ptr использует подсчет ссылок для определения, когда удаляется объект, на который он ссылается. Это в основном сделай сам мусор. Документы здесь: http://www.boost.org/doc/libs/1_42_0/libs/smart_ptr/smart_ptr.htm

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

Просто укажу здесь, но

str = _str;

не будет компилироваться (вы пытаетесь присвоить _str, который является значением строки, переданной по ссылке, str, который является адресом строки). Если бы вы хотели это сделать, вы бы написали:

str = &_str;

(и вам придется изменить либо _str, либо str, чтобы совпадение констант).

Но тогда, как сказала ваша интуиция, вы бы вытекли из памяти любого строкового объекта, на который уже указывал str.

Как указывалось ранее, когда вы добавляете переменную в класс в C ++, вы должны подумать о том, принадлежит ли переменная объекту или чему-то еще.

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

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

Другие люди объяснят это лучше меня, потому что I не очень-то доволен этим. В результате я часто пишу такой код:

class Foo {

private :
   Bar & dep_bar_;
   Baz & dep_baz_;

   Bing * p_bing_;

public:
   Foo(Bar & dep_bar, Baz & dep_baz) : dep_bar_(dep_bar), dep_baz_(dep_baz) {
       p_bing = new Bing(...);
   }

   ~Foo() {
     delete p_bing;
   }

То есть, если объект зависит от чего-либо в смысле «Java» / «Ioc» (объекты существуют в другом месте, вы его не создаете, и вам нужно только вызвать метод для него), я бы сохранил зависимость в качестве ссылки, используя dep_xxxx.

Если бы я создал объект, я бы использовал указатель с префиксом p_.

Это просто для того, чтобы сделать код более "немедленным". Не уверен, что это помогает.

Просто мой 2с.

Удачи с памятью mgt, вы правы, что - сложная часть, пришедшая из Java; не пишите код, пока не почувствуете себя комфортно, иначе вы будете часами гоняться за segaults.

Надеюсь, это поможет!

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

Когда вы переназначаете указатель на новый объект, нужно ли сначала вызывать delete для старого объекта, чтобы избежать утечки памяти? Моя интуиция говорит мне, что да, но я хочу получить конкретный ответ, прежде чем двигаться дальше.

Да. Если это необработанный указатель, вы должны сначала удалить старый объект.

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

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

Три комментария:

Вам также нужен деструктор.

~MyClass() 
{
    delete str;
}

В этом случае вам действительно не нужно использовать память, выделенную для кучи. Вы можете сделать следующее:

class MyClass {
    private: 
        std::string str;

    public:
        MyClass (const std::string &_str) {
            str= _str;
        }

        void ChangeString(const std::string &_str) {
            str = _str;
};

Вы не можете сделать закомментированную версию. Это было бы утечкой памяти. Java заботится об этом, потому что имеет сборку мусора. C ++ не имеет этой функции.

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