No programa a seguir, pretendo copiar o char* line
conteúdo de um objeto para outro strcpy
. No entanto, quando o programa termina, o destruidor de obj2
obras funciona bem, mas o dtor de obj
falhas. O gdb mostra endereços diferentes line
para os dois objetos.
class MyClass {
public:
char *line;
MyClass() {
line = 0;
}
MyClass(const char *s) {
line = new char[strlen(s)+1];
strcpy(line, s);
}
~MyClass() {
delete[] line;
line = 0;
}
MyClass &operator=(const MyClass &other) {
delete[] line;
line = new char[other.len()+1];
strcpy(line, other.line);
return *this;
}
int len(void) const {return strlen(line);}
};
int main() {
MyClass obj("obj");
MyClass obj2 = obj;
MyClass obj1; MyClass obj2 = obj1;
ainda irá falhar porque você chamarástrlen(obj1.line)
qual éstrlen(NULL)
. Como seriaMyClass obj1; obj1.len();
.MyClass obj1; obj1.len();
é um comportamento indefinido para chamarstrlen
um ponteiro nulo.Respostas:
Com
você não tem atribuição, você tem cópia-construção . E você não segue as regras de três, cinco ou zero, pois não possui um construtor de cópias; portanto, o gerador gerado por padrão apenas copia o ponteiro.
Isso significa que, depois disso, você terá dois objetos cujo
line
ponteiro apontará exatamente para a mesma memória. Isso levará a um comportamento indefinido quando um dos objetos for destruído, deixando o outro com um ponteiro inválido.A solução ingênua é adicionar um construtor de cópia que faça uma cópia profunda da própria string, da mesma forma que o operador de atribuição está fazendo.
Uma solução melhor seria usar
std::string
suas strings e seguir a regra do zero.fonte
Você precisa criar um construtor de cópias. Isso tem que fazer a regra de 3/5 . Você está criando
obj2
, o que significa que um construtor de cópias é chamado, não o operador de atribuição de cópias.Como você não possui um construtor de cópias, é feita uma cópia "superficial". Isso significa que
line
é copiado pelo valor. Como é um ponteiro, ambosobj
eobj2
estão apontando para a mesma memória. O primeiro destruidor é chamado e apaga bem essa memória. O segundo construtor é chamado e ocorre uma exclusão dupla, causando sua falha de segmentação.Ao lidar com C-Strings, você absolutamente não pode perder o caractere nulo. A questão é que é extremamente fácil perder. Você também não tinha um protetor de atribuição automática no operador de atribuição de cópias. Isso poderia ter levado você a acidentalmente destruir um objeto. Eu adicionei um
size_
membro e usei, emstrncpy()
vez de,strcpy()
porque poder especificar um número máximo de caracteres é incrivelmente importante no caso de perder um caractere nulo. Não evitará danos, mas os mitigará.Há outras coisas que eu gostei de usar a Inicialização Padrão de Membros (a partir do C ++ 11) e a lista de inicialização de membros construtores . Muito disso se torna desnecessário se você puder usar
std::string
. C ++ pode ser "C com classes", mas vale a pena dedicar um tempo para realmente explorar o que a linguagem tem a oferecer.Algo que um construtor e destruidor de cópias de trabalho nos permite fazer é simplificar nosso operador de atribuição de cópias usando o "idioma de copiar e trocar".
Link para explicação .
fonte