C ++ Class Copy (указатель копирования) - PullRequest
10 голосов
/ 17 февраля 2011

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

Мой вопрос:можно предположить, что «копия указателя» в этом случае просто создает новый указатель (динамическое выделение памяти) того же типа?Например, новый указатель - это просто новое выделение, содержащее произвольный адрес памяти, и нужно позаботиться о том, чтобы указать этот новый указатель на соответствующий адрес памяти?

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

С уважением,

Чад

Ответы [ 5 ]

18 голосов
/ 17 февраля 2011

Указатель будет просто скопирован как значение - поэтому оба класса будут указывать на одну и ту же исходную память, новое выделение не происходит. Мелкая копия - это то, что язык делает по умолчанию.

Если вам нужно выделить новую память и сделать копию данных, вы должны сделать это самостоятельно в конструкторе копирования. Глубокая копия - вы должны сделать это сами

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

8 голосов
/ 17 февраля 2011

Во-первых, указатель в вашем классе является статическим (т. Е. Компилятор знает, что в этом классе есть указатель и какой у него размер, поэтому при создании экземпляра класса динамическое выделение памяти не требуется).

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

#include <iostream>

class A {
   public:
       int *p;
};

int main() {
    A a,b;
    a.p = new int(10);
    b = a;

    std::cout << "*(a.p) = " << *(a.p) << std::endl; // 10
    std::cout << "*(b.p) = " << *(b.p) << std::endl; // 10

    *(b.p) = 3;

    std::cout << "*(a.p) = " << *(a.p) << std::endl; // 3
    std::cout << "*(b.p) = " << *(b.p) << std::endl; // 3

    return 0;
}

Теперь, если вы хотите выделить новую память при копировании, вам нужно написать конструктор копирования и конструктор назначения копирования:

#include <iostream>

class A {
   public:
       int *p;

       A() : p(0) {}
       A(const A& other) { // copy constructor
           p = new int(*other.p);
       }

       A& operator=(const A& other) { // copy assignment constructor
           // protect against self assignment
           if (this != &other) {
               if (p != 0) {
                   *p = *other.p;
               } else { // p is null - no memory allocated yet
                   p = new int(*other.p);
               }
           }
           return *this;
       }

       ~A() { // destructor
           delete p;
       }
};


int main() {
    A a,b;
    a.p = new int(10);
    b = a;

    std::cout << "*(a.p) = " << *(a.p) << std::endl; // 10
    std::cout << "*(b.p) = " << *(b.p) << std::endl; // 10

    *(b.p) = 3;

    std::cout << "*(a.p) = " << *(a.p) << std::endl; // 10
    std::cout << "*(b.p) = " << *(b.p) << std::endl; // 3

    return 0;
}

Когда вы сделаете это, вытакже следует написать деструктор (см. Правило трех ), потому что память, которая выделяется в конструкторах копирования / копирования, должна быть удалена, если класс уничтожен:

3 голосов
/ 17 февраля 2011

Указатели не создают динамическое выделение памяти. Указатели и выделения - это совершенно разные вещи.

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

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

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

2 голосов
/ 17 февраля 2011

Скопированный указатель будет указывать на точно такой же адрес.Нового распределения нет.

1 голос
/ 17 февраля 2011

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

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

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