If you really don't want to use std::string
, your best bet is something in the line of (live at coliru):
#include<iostream>
#include<cstring>
using namespace std;
class Animal {
private:
// copy a string
inline static char* dstr(const char* string) {
if( !string ) return NULL;
size_t l = strlen(string);
if( !l ) return NULL;
return strcpy(new char[++l], string);
}
protected:
int age;
char* name;
public:
// initialize an "empty" Animal
Animal() : age(0), name(NULL) {}
// initialize an animal by age and name
Animal(int _age, const char* _name): age(_age), name(dstr(_name)) {}
// initialize an animal from another animal:
// copy the name string
Animal(const Animal& _a): age(_a.age), name(dstr(_a.name)) {}
// assign an animal from another animal:
// first delete the string you have, then copy the string
Animal& operator=(const Animal& _a) {
// for exception-safety, save the old "name" pointer,
// then try to allocate a new one; if it throws, nothing happens
// to *this...
char* oldname = name;
name = dstr(_a.name);
age = _a.age;
delete[] oldname;
return *this;
}
// if C++11
// we have something called "move" constructor and assignment
// these are used, for instance, in "operator>>" below
// and they assume that _a will soon be deleted
Animal(Animal&& _a): Animal() {
swap(age, _a.age);
swap(name, _a.name);
}
Animal& operator=(Animal&& _a) {
swap(age, _a.age);
swap(name, _a.name);
return *this;
}
~Animal() { delete[] name; }
friend ostream& operator <<(ostream& out, const Animal& a);
};
istream& operator >>(istream& in, Animal& a) {
const size_t MAX_ANIMAL_NAME = 2048;
int age;
char n[MAX_ANIMAL_NAME+1];
if( in.getline(n, MAX_ANIMAL_NAME) >> age )
a = Animal(age, n);
return in;
}
ostream& operator <<(ostream& out, const Animal& a) {
return out<<a.name<<endl<<a.age<<endl;
}
int main() {
Animal a { 23, "bobo" };
cout<<a;
cin>>a;
cout<<a;
}
This does not leak memory, does not have undefined behaviours, and does not have buffer overruns.
You can also segregate the "need to manage memory" to a separate class:
#include<iostream>
#include<cstring>
using namespace std;
class AnimalName {
private:
char *n;
inline static char* dstr(const char* string) {
if( !string ) return NULL;
size_t l = strlen(string);
if( !l ) return NULL;
return strcpy(new char[++l], string);
}
public:
AnimalName() : AnimalName(NULL) {}
AnimalName(const char *_n) : n(dstr(_n)) {}
AnimalName(const AnimalName& _n) : n(dstr(_n.n)) {}
// see exception-safety issue above
AnimalName& operator=(const AnimalName& _n) { char *on = n; n = dstr(_n.n); delete[] on; return *this; }
AnimalName(AnimalName&& _n) : AnimalName() { swap(n, _n.n); }
AnimalName& operator=(AnimalName&& _n) { swap(n, _n.n); return *this; }
~AnimalName() { delete[] n; }
operator const char*() const { return n; }
friend istream& operator>>(istream& i, AnimalName& n) {
const size_t MAX_ANIMAL_NAME = 2048;
char name[MAX_ANIMAL_NAME+1];
if( i.getline(name, MAX_ANIMAL_NAME) )
n = name;
return i;
}
};
class Animal {
protected:
int age;
AnimalName name;
public:
// initialize an "empty" Animal
Animal() : age(0) {}
// initialize an animal by age and name
Animal(int _age, const char* _name): age(_age), name(_name) {}
friend ostream& operator <<(ostream& out, const Animal& a) {
return out<<a.name<<endl<<a.age<<endl;
}
};
istream& operator >>(istream& in, Animal& a) {
AnimalName n;
int age;
if( in >> n >> age )
a = Animal(age, n);
return in;
}
int main() {
Animal a { 23, "bobo" };
cout<<a;
cin>>a;
cout<<a;
return 0;
}
This way you get to follow the "rule of zero" (basically, classes that do not have the sole responsibility of managing memory/resources should not manage memory and therefore should not implement copy/move-constructors, assignments, or destructors.)
And that takes us to the real reason why you should use std::string
: it not only does the memory management for you, but it also takes good care of your IO needs, eliminating the need for a "maximum animal name" in your example:
#include<iostream>
#include<string>
using namespace std;
class Animal {
protected:
string name; // name first, for exception-safety on auto-gen assignment?
int age;
public:
// initialize an "empty" Animal
Animal() : age(0) {}
// initialize an animal by age and name
Animal(int _age, const string& _name): age(_age), name(_name) {}
friend ostream& operator <<(ostream& out, const Animal& a) {
return out<<a.name<<endl<<a.age<<endl;
}
};
istream& operator >>(istream& in, Animal& a) {
string n;
int age;
if( getline(in, n) >> age )
a = Animal(age, n);
return in;
}
int main() {
Animal a { 23, "bobo" };
cout<<a;
cin>>a;
cout<<a;
}