классы с указателями и без переопределенного конструктора копирования - PullRequest
6 голосов
/ 06 декабря 2011

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

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

И в качестве побочного вопроса, был бы я прав, думая, что общий ресурсуказатель будет указывать все классы на один набор объектов, но безопасным способом?

Ответы [ 4 ]

3 голосов
/ 06 декабря 2011

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

Таким образом, два объекта (оригинал и копия) будут иметь поля указателя, указывающие на один и тот же объект.

2 голосов
/ 06 декабря 2011

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

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

Пример:

struct Message
{
    Message(const LogType_E & type_in = LOG_ERROR, const unsigned int & domain_in = 0, const int & msgId_in = 0, const char * msg_in = "");
    int myMsgID;            //!< message id
    unsigned int myDomain;  //!< message domain
    LogType_E myType;       //!< message type
    char * myMsg;           //!< actual message 

    ~Message()
    {
        if(myMsg != NULL) delete [] myMsg;
    }

    Message(const Message &);

    const Message& operator=(const Message & rhs);
};

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

Реализация будет выглядеть так:

Message::Message(const Message & cp_in):myType(cp_in.myType), myDomain(cp_in.myDomain), myMsgID(cp_in.myMsgID), myMsg(NULL)
{
    if(cp_in.myMsg != NULL)
    {
        myMsg = new char[strlen(cp_in.myMsg)+1];
        memcpy (myMsg, cp_in.myMsg, strlen(cp_in.myMsg)+1); 
    }
}

const Message & Message::operator =(const AX::Base::Log::Message &cp_in)
{
    if (this == &cp_in) // protect against invalid self-assignment
        return *this;

    //deallocate old memory
    if(myMsg != NULL) delete [] myMsg;

    if(cp_in.myMsg != NULL)
    {
    //allocate new memory and copy the elements
        myMsg = new char[strlen(cp_in.myMsg)+1];
        memcpy (myMsg, cp_in.myMsg, strlen(cp_in.myMsg)+1); 
    }

    // copy other data members
    myType = cp_in.myType;
    myDomain = cp_in.myDomain;
    myMsgID = cp_in.myMsgID;

    return *this;
}

Тем не менее, пожалуйста, используйте std::string, чтобы избежать всего этого - это было просто примером концепции.

1 голос
/ 06 декабря 2011

Представьте, что у вас есть класс, подобный такому, который демонстрирует проблему, которую вы задали в вопросе

class Foo{};

class Bar
{
public:
    Foo* mFoo;
    Bar() : mFoo( new Foo() ) {}
    ~Bar() { delete mFoo;}
};

И такой код

Bar x ;
Bar y = x;

Приведенный выше код вызовет дамп ядрапотому что и y, и x будут указывать на один и тот же Foo , а деструктор попытается удалить один и тот же Foo дважды.

ALTERNATIVE 1

Объявлять, но не предоставлять определение, чтобы Bar никогда не копировал конструктор и не назначал его.Это гарантирует, что Bar y = x будет иметь ошибку связи, так как вы спроектировали класс, который не будет копироваться.

class Bar
{
public:
    Foo* mFoo;
    Bar() : mFoo( new Foo() ) {}
    ~Bar() { delete mFoo;}

    Bar(const Bar &);
    Bar& operator= (const Bar &);
};

ALTERNATIVE 2

Предоставьте конструктор копирования иоператор присваивания, который делает правильную вещь.Вместо того, чтобы компилятор предоставил реализацию по умолчанию копирования и присваивания, которые делают поверхностное копирование, вы дублируете Foo , чтобы и x, и y имели свои собственные Foo

class Bar
{
public:
    Foo* mFoo;
    Bar() : mFoo( new Foo() ) {}
    ~Bar() { delete mFoo;}

    Bar(const Bar & src)
    {
        mFoo = new Foo( *(src.mFoo) );
    }

    Bar& operator= (const Bar & src)
    {
        mFoo = new Foo( *(src.mFoo) );
        return *this;
    }
};

ALTERNATIVE 3 (BEST)

Используйте C ++ 11 shared_ptr или boost и опустите копирование и присвоение в качестве компилятора по умолчанию, при условии, что вы будете делать правильные вещи как shared_ptr ref ref count и удалит Foo только один раз, даже если x и y совместно используют один и тот же Foo .Также обратите внимание, что ~Bar не требуется явной очистки, так как mFoo будет автоматически удален в деструкторе std::shared_ptr<Foo>, когда счет Foo станет равным нулю.

class Bar
{
public:
    std::shared_ptr<Foo> mFoo;
    Bar() :mFoo( new Foo() ) {}
    ~Bar() { }
};
1 голос
/ 06 декабря 2011

Пусть код говорит, чтобы прояснить ситуацию:

struct X
{
    int data;
    int *ptr;

    X() : ptr(&data) {}
};

X a;
X b = a; // yes, `a.ptr` points to `b.data`!

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

Использовать указатели на элементы

Исправляется следующим образом:

struct X
{
    int data;
    int X::*ptr;

    X() : ptr(&X::data) {}
};

X a;
X b = a; // now, `a.ptr` points to `a.data`

Расширение этого примера еще несколькими подсказками по использованию https://ideone.com/F0rC3

a.ptr = &X::data2;  // now `a.ptr` points to `a.data2`
                    //     `b.ptr` points to `b.data1`
b = a;              //     `b.ptr` points to `b.data2` too

// Usage hint:
int deref = a.*(a.ptr); // gets the field pointed to by a.ptr, from the instance a
    deref = b.*(b.ptr); // gets the field pointed to by b.ptr, from the instance b

// but of course you could get fancy and do
    deref = a.*(b.ptr); // gets the field pointed to by b.ptr, **but** from the instance a

Это сделало бы то, что вы, вероятно, хотите. Хотя, почему вы хотите, чтобы был за мной (и, возможно, за C ++)

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