С++ висячий указатель/глубокая копия/мелкая путаница с копией

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

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

#include<iostream>

using namespace std;

class Player
{
    public:
        char * name;
        char * countryName;
        char * gameName;
        int age;

        Player()
        {
        }

        Player(char * n, char * c, char * g,int a)
        {
            name=n;
            countryName=c;
            gameName=g;
            age=a;
        }
};


void printAddresses(Player p,Player p3)
{
    cout<<endl<<&p3<<endl<<&p<<endl;
    cout<<endl<<&p3.name<<endl<<&p.name<<endl;
    cout<<endl<<&p3.countryName<<endl<<&p.countryName<<endl;
    cout<<endl<<&p3.gameName<<endl<<&p.gameName<<endl;
    cout<<endl<<&p3.age<<endl<<&p.age<<endl;
}

int main()
{
    Player *p2=new Player;

    Player *p4=p2;

    // p2 is a pointer and p4 is also a pointer initialized from p2. But the following function prints the memory addresses, and it shows that both objects have different memory addresses. And data members also have different memory locations
    printAddresses(*p4,*p2);

    return 0;
}

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

Итак, в каком случае здесь может возникнуть проблема с оборванным указателем? ИЛИ как я могу сделать неглубокую копию здесь? Этот стандарт/версия С++ (написанная ниже) ведет себя так, или я что-то упустил?

ОС: линукс 15 мин.

Вывод g++ --version:

g++ (Ubuntu/Linaro 4.7.3-1ubuntu1) 4.7.3
Copyright (C) 2012 Free Software Foundation, Inc.

person Syed Aqeel Ashiq    schedule 21.10.2013    source источник
comment
вы передаете по значению. это: &p и &p3 имеют свои адреса. printAdresses(Player *p, Player *p3) { cout << p3 << p << endl; ... и вызов, как printAddresses(p4, p2), должен сделать это.   -  person user1810087    schedule 21.10.2013
comment
Не имеет отношения к вашему конкретному вопросу, но сделайте себе одолжение и прочитайте это и это.   -  person BoBTFish    schedule 21.10.2013


Ответы (1)


Проблема в том, что вы передаете по значению.
Таким образом, ваш параметр имеет свои собственные адреса, которые отличаются от ваших указателей. То, что вы хотите, проходит по указателю или ссылке:

см. пример здесь:

// by pointer
void printAdressesPtr(int* p1, int* p2) {
    std::cout << "p1 - ptr: " << p1 << " - value: " << *p1 << std::endl;
    std::cout << "p2 - ptr: " << p2 << " - value: " << *p2 << std::endl;

    std::cout << "p1 - ptr ref: " << &p1 << std::endl;
    std::cout << "p2 - ptr ref: " << &p2 << std::endl;
}

// by reference
void printAdressesRef(int& r1, int& r2) {
    std::cout << "r1 - ref: " << &r1 << " - value: " << r1 << std::endl;
    std::cout << "r2 - ref: " << &r2 << " - value: " << r2 << std::endl;
}

// by value (copy)
void printAdressesVal(int v1, int v2) {
    std::cout << "v1 - ref: " << &v1 << " - value: " << v1 << std::endl;
    std::cout << "v2 - ref: " << &v2 << " - value: " << v2 << std::endl;
}

int main() {
    int* ptr1 = new int(123);
    int* ptr2 = ptr1;

    printAdressesPtr(ptr1, ptr2);
    printAdressesRef(*ptr1, *ptr2);  
    printAdressesVal(*ptr1, *ptr2);

    return 0;
}

Как видите, все адреса заданных указателей/ссылок (ptr1 && ptr2) совпадают, как и значения. Но адреса для параметров разные.

Редактировать

Копирование указателей всегда поверхностное копирование. Означает, что все копии строк char*, но age не поверхностны. Для глубокой копии каждому экземпляру Player требуется отдельная память, выделенная для каждого значения.

Поскольку это С++, вам, вероятно, следует использовать std::string вместо char*:

class Player {
public:
  std::string name;
  std::string countryName;
  std::string gameName;
  int         age;

  Player() {}

  Player(std::string const& n, std::string const& c, std::string const& g, int a)
    : name(n), countryName(c), gameName(g), age(a) {} // this are deep copies

  // copy constructor
  Player(Player const& cpy)
    : name(cpy.name)
    , countryName(cpy.countryName)
    , gameName(cpy.gameName)
    , age(cpy.age) {} // this are deep copies
};

В противном случае вам придется следить за выделением и удалением самостоятельно. это будет выглядеть так:

class Player {
public:
  char* name;
  char* countryName;

  Player()
    : name(nullptr), countryName(nullptr) {}

  Player(char* n, char* c) {
    // deep copy. name has his own memmory allocated and the value is copied 
    // from n to name
    name = new char[strlen(n) + 1]; 
    strcpy(name, n);

    // shallow copy. contryName and c have the same address of the value.
    // changing contryName of P2 is also changing contryName of P1. also there is no
    // guarantee that enough space is allocated.
    contryName = c;
  }


  // copy constructor
  Player(Player const& cpy) {
    // this is a deep copy. name has his own memmory allocated and the value is copied
    // from n to name
    name = new char[strlen(cpy.name) + 1]; 
    strcpy(name, cpy.name);

    // shallow copy. contryName and c have the same address of the value.
    // changing contryName of P2 is also changing contryName of P1. Also there is no
    // guarantee that enough space is allocated...
    contryName = c;
  }

  ~Player() {
    if(name != nullptr)
      delete name;
    name = 0;

    // this will also delete contryName of P1. access from P1 is undefined behavior...
    if(contryName != nullptr)
      delete[] contryName;
    contryName = 0;
  }
};

Также вам нужно перегрузить operator=, так как приведенный выше пример предназначен только для конструктора...

Предпочтительнее использовать std::string .

Редактировать2:

Я забыл упомянуть, что у вас нет конструктора копирования. Player p1=p2; // this is initialization нужен конструктор копирования, иначе будет использоваться сгенерированный компилятором, который в конечном итоге окажется в неглубоких копиях.

person user1810087    schedule 21.10.2013
comment
Ok. Пожалуйста, скажите мне еще одну вещь: Player p; Player p2=p; Теперь p2 на самом деле является глубокой копией p? И все члены p2 являются глубокими копиями членов p? Или, другими словами, может ли копирование двух подобных объектов потенциально привести к проблеме с оборванным указателем? - person Syed Aqeel Ashiq; 23.10.2013
comment
@djaqeel я кое-что забыл о конструкторе копирования. я отредактировал код и сделал второе редактирование. :) - person user1810087; 23.10.2013