Правильный способ переназначения указателей в c++

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

Я хочу убедиться, что у меня есть эти основные идеи, прежде чем двигаться дальше (я исхожу из фона 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 ответов


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

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;

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


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

class MyClass
{
private:
    std::string str;

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

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

просто указывая здесь, но

str = _str;

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

str = &_str;

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

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

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

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

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

другие люди объяснят это лучше, чем я, потому что я Я не очень комфортно с ним. То, что я в конечном итоге делаю, - это писать код следующим образом:

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_.

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

только мой 2c.

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

надеюсь, что это помогает !


общее правило в 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


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

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<> во всем вы должны быть в порядке.)

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


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

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

~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++ не имеет этой функции.


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

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

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