2015-04-22 1 views
0

Мы должны использовать copy-on-write в нашем школьном проекте. Я экспериментировал с очень простым классом, но без везения. У меня есть это:C++ - основная реализация копирования на запись

#include <iostream> 
#include <cstdlib> 
#include <cstring> 

using namespace std; 

class CPerson { 
    public: 
      CPerson(); 
      CPerson (const CPerson&); 
      ~CPerson(); 
     char* m_name; 
     char* m_surname; 
     int m_refs; 

     void rename (const char*, const char*); 
}; 

CPerson :: CPerson() : m_name(NULL), m_surname(NULL), m_refs(1) {} 

CPerson :: CPerson (const CPerson& src) : m_name (src.m_name), m_surname (src.m_surname), m_refs(src.m_refs+1) {} // supposed to be a shallow copy 

CPerson :: ~CPerson() { 
    if (m_refs == 1) { 
     delete [] m_name; 
     delete [] m_surname; 
    } 
    else --m_refs; 
} 

void CPerson :: rename (const char* name, const char* surname) { 
    delete [] m_name; 
    delete [] m_surname; 

    m_name = new char [strlen(name)+1]; 
    m_surname = new char [strlen(surname)+1]; 

    strcpy (m_name, name); 
    strcpy (m_surname, surname); 
} 

int main() { 
    CPerson a; 
    a.rename ("Jack", "Smith"); 
    cout << a.m_name << " " << a.m_surname << endl; 

    CPerson b(a); 
    cout << a.m_name << " " << a.m_surname << endl; 
    cout << b.m_name << " " << b.m_surname << endl; 
    // good so far... 

    a.rename ("John", "Anderson"); // should rename both 'a' and 'b' 
    cout << a.m_name << " " << a.m_surname << endl; 
    cout << b.m_name << " " << b.m_surname << endl; 

    // prints random values 



    return 0; 
} 

Это странно, потому что, когда я вынимаю couts, все работает КИ (без утечек, без ошибок с помощью Valgrind).

Любая помощь будет оценена по достоинству.

+2

Ваша реализация распадается на этот простой code: 'int main() {CPerson * a = новый CPerson; CPerson b (* a); удалить a; cout << b.m_name; } 'Другими словами, первый созданный один уничтожается, а второй (' b') все еще жив и теперь будет иметь доступ к памяти, которая была освобождена. Пора посмотреть на ваш дизайн, особенно на эту переменную подсчета ссылок (которая на самом деле не разделяется экземплярами). – PaulMcKenzie

+2

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

+1

*** a.rename («Джон», «Андерсон»); // должен переименовывать оба 'a' и 'b' ***. Я бы ожидал, что переименование не изменится 'b', если вы делаете копию при записи. – drescherjm

ответ

2

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

Попробуйте что-то больше, как это вместо:

#include <iostream> 
#include <string> 

using namespace std; 

struct SPersonData 
{ 
    string m_name; 
    string m_surname; 
    int m_refcnt; 

    SPersonData() : m_refcnt(0) {} 

    void incRef() { ++m_refcnt; } 
    void decRef() { if (--m_refcnt == 0) delete this; } 
}; 

class CPerson 
{ 
private: 
    SPersonData *m_data; 

public: 
    CPerson(); 
    CPerson (const CPerson&); 
    ~CPerson(); 

    CPerson& operator= (const CPerson&); 

    string getName() const; 
    string getSurname() const; 

    void rename (const string&, const string&); 
}; 

CPerson::CPerson() 
    : m_data(NULL) {} 

CPerson::CPerson (const CPerson& src) 
    : m_data (src.m_data) 
{ 
    if (m_data) m_data->incRef(); 
} 

CPerson::~CPerson() 
{ 
    if (m_data) m_data->decRef(); 
} 

CPerson& operator= (const CPerson &src) 
{ 
    if (this != &src) 
    { 
     if (m_data) m_data->decRef(); 
     m_data = src.m_data; 
     if (m_data) m_data->incRef(); 
    }  
    return *this; 
} 

string CPerson::getName() const 
{ 
    if (m_data) return m_data->m_name; 
    return string(); 
} 

string CPerson::getSurname() const 
{ 
    if (m_data) return m_data->m_surname; 
    return string(); 
} 

void CPerson::rename (const string &name, const string &surname) 
{ 
    if ((m_data) && (m_data->m_refcnt > 1)) 
    { 
     m_data->decRef(); 
     m_data = NULL; 
    } 

    if (!m_data) 
    { 
     m_data = new SPersonData; 
     m_data->incRef(); 
    } 

    m_data->m_name = name; 
    m_data->m_surname = surname; 
} 

который может быть значительно упрощена в C++ 11 и позже, используя std::shared_ptr для управления счетчик ссылок:

#include <iostream> 
#include <string> 
#include <memory> 

using namespace std; 

struct SPersonData 
{ 
    string m_name; 
    string m_surname; 
}; 

class CPerson 
{ 
public: 
    shared_ptr<SPersonData> m_data; 

    string getName() const; 
    string getSurname() const; 

    void rename (const string&, const string&); 
}; 

string CPerson::getName() const 
{ 
    if (m_data) return m_data->m_name; 
    return string(); 
} 

string CPerson::getSurname() const 
{ 
    if (m_data) return m_data->m_surname; 
    return string(); 
} 

void CPerson::rename (const string &name, const string &surname) 
{ 
    if (!((m_data) && m_data.unique())) 
     m_data = make_shared<SPersonData>(); 

    m_data->m_name = name; 
    m_data->m_surname = surname; 
} 

В любом случае, ваш тест будет выглядеть следующим образом:

int main() 
{ 
    CPerson a; 
    a.rename ("Jack", "Smith"); 
    cout << a.getName() << " " << a.getSurname() << endl; 

    CPerson b(a); 
    cout << a.getName() << " " << a.getSurname() << endl; 
    cout << b.getName() << " " << b.getSurname() << endl; 
    // good so far... 

    a.rename ("John", "Anderson"); // should rename only 'a' not 'b' 
    cout << a.getName() << " " << a.getSurname() << endl; 
    cout << b.getName() << " " << b.getSurname() << endl; 

    return 0; 
} 
+0

Я думаю, что счетчик ссылок может быть помещен в 'class CPerson', если мы используем' int * ref = new int' вместо ' int ref' – bigxiao

+0

, и так действуют интеллектуальные указатели, потому что умный указатель не смог поместить подсчет ссылок в пользовательский класс – bigxiao

+0

@bigxiao посмотреть фактическую реализацию 'shared_ptr'. Он обертывает пользовательские данные внутри блока управления, который также содержит счетчик ссылок, а затем передает указатель на блок управления. Как и в моем ответе. –