Надлежащий способ повторно присвоить указатели в C++

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

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

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

Например, позвольте, говорят, что у Вас был класс, который сохранил указатель на строку

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, который был бы корректен?

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

Любой совет был бы значительно appricated :D

6
задан vimalloc 16 April 2010 в 16:11
поделиться

7 ответов

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

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

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

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


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

*str = _str;

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

str = &_str;

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

13
ответ дан 8 December 2019 в 12:18
поделиться

Общее правило C ++ состоит в том, что для каждого объекта, созданного с помощью «нового», должно быть «удаление». Убедитесь, что это всегда происходит в жесткой части;) Современные программисты на 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
ответ дан 8 December 2019 в 12:18
поделиться

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

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

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

0
ответ дан 8 December 2019 в 12:18
поделиться

Просто определяю здесь, но

str = _str;

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

str = &_str;

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

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

Как указывалось ранее, когда вы добавляете переменную в класс в 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_.

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

Только мой 2с.

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

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

1
ответ дан 8 December 2019 в 12:18
поделиться

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

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 * f; в определении класса. В этом случае конструктор копирования должен быть A (const A & a): foo (a.foo) {} , поскольку интеллектуальный указатель позаботится об удалении Foo , когда все копии общего указателя, указывающие на него, будут уничтожены. (Здесь тоже могут возникнуть проблемы, особенно если вы смешиваете shared_ptr <> с любой другой формой указателя, но если вы будете придерживаться shared_ptr <> , вы должны ОК.)

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

1
ответ дан 8 December 2019 в 12:18
поделиться

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

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

~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 ++ такой возможности нет.

0
ответ дан 8 December 2019 в 12:18
поделиться

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

class MyClass
{
private:
    std::string str;

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

void ChangeString(const std::string & astr)
{
    str = astr;
}
....
};
3
ответ дан 8 December 2019 в 12:18
поделиться
Другие вопросы по тегам:

Похожие вопросы: