В настоящее время я изучаю C ++ из C # / Java, используя Visual Studio 2017.
У меня есть вопрос относительно создания объектов в куче и ссылки на них должным образом в будущем. До сих пор я сталкивался с несколькими уроками и способами ведения дел. Некоторые рекомендуют использовать умные указатели как можно больше, другие клянутся инструмент дьяволов.
Моя текущая главная выглядит так:
//Main
Person *makePerson()
{
string name;
int age;
cout << "Input name: ";
cin >> name;
cout << "Input age: ";
cin >> age;
return new Person(name, age);
}
Child *makeChild(Person &parent)
{
return new Child(*makePerson(), &parent);;
}
int main()
{
cout << "---Input parent data---" << endl;
Person *person = makePerson();
cout << "printing: " << *person << endl;
cout << "---Input child data---" << endl;
Child *child = makeChild(*person);
cout << "printing: " << *child << endl;
cout << "---end of main---" << endl;
delete person;
delete child;
return 0;
}
Функция обрабатывает ввод персональных данных и возвращает указатель на новый объект Person. Затем у меня есть функция, которая обрабатывает создание дочернего объекта, беря родительскую ссылку и запрашивая у makePerson оставшиеся данные.
Можно ли считать это хорошим C ++? Как я могу сделать это лучше? Я был бы очень признателен за примеры кода.
Как некоторые уже предложили, я мог бы заменить необработанные указатели shared_ptr<Person> person
(тяжелый) или unique_ptr<Person>
(лучше, чем поделился).
Это код для персоны и дочерних классов. Обратите внимание, что у Child есть необработанный указатель типа Person *parent
,
//header
class Person
{
protected:
std::string name;
int age;
public:
Person();
Person(const Person& other);
Person(std::string inName, int inAge);
~Person();
virtual void print() const;
std::string getName() const;
int getAge() const;
Person &operator=(const Person &other);
//overload print functionality, act as if it was toString
friend std::ostream &operator<<(std::ostream &out, const Person &p);
};
//cpp
Person::Person() : name(""), age(0) {
std::cout << "Person empty constructor" << std::endl;
}
Person::Person(std::string inName, int inAge) : name(inName), age(inAge) {
std::cout << "Person (" << name << ") default constructor" << std::endl;
}
Person::Person(const Person & other) : name(other.name), age(other.age) {
std::cout << "Person (" << name << ") copy constructor" << std::endl;
}
Person::~Person() {
std::cout << "Person (" << name << ") destructor" << std::endl;
}
void Person::print() const {
std::cout << name << ", " << age << std::endl;
}
std::string Person::getName() const
{
return name;
}
int Person::getAge() const
{
return age;
}
Person & Person::operator=(const Person & other) {
std::cout << "Person (" << other.name << ") assignment constructor" << std::endl;
name = other.name;
age = other.age;
return *this;
}
std::ostream &operator<<(std::ostream &out, const Person &p) {
return out << p.name << ", " << p.age;
}
Ребенок — это человек, и для ребенка имеет смысл знать, кто является родителем ребенка. Однако я не уверен, как справиться с этим «знанием». Вот код, который я использую для дочернего класса:
//Header
class Child : public Person
{
private:
const Person *parent;
public:
Child();
Child(std::string name, int age);
Child(std::string name, int age, const Person *parent);
Child(const Child &child, const Person *parent);
Child(const Person &person);
~Child();
Child &operator=(const Child &other);
void print() const;
friend std::ostream &operator<<(std::ostream &out, const Child &c);
};
//cpp
Child::Child() {
std::cout << "Child empty constructor" << std::endl;
}
Child::Child(std::string name, int age) : Person(name, age), parent(nullptr) {
std::cout << "Orphan (" << name << ") constructor" << std::endl;
}
Child::Child(std::string name, int age, const Person *parent) :
Person(name, age), parent(parent) {
std::cout << "Child (" << name << ") default constructor" << std::endl;
}
Child::Child(const Child &child, const Person *parent) :
Person(child.name, child.age), parent(parent) {
std::cout << "Child (" << child.name << ") copy constructor" << std::endl;
}
Child::Child(const Person &person) : Person(person), parent(nullptr) {
std::cout << "Child from person (" << name << ") constructor" << std::endl;
}
Child::~Child() {
std::cout << "Child (" << name << ") destructor" << std::endl;
}
Child & Child::operator=(const Child & other) {
name = other.name;
age = other.age;
parent = other.parent;
std::cout << "Child (" << name << ") assignment constructor" << std::endl;
return *this;
}
void Child::print() const {
if(parent)
std::cout << *this << " is child of " << *parent << std::endl;
else
std::cout << *this << " is orphan" << std::endl;
}
std::ostream &operator<<(std::ostream &out, const Child &c) {
return out << c.name << ", " << c.age << " is " <<
(c.parent ? ("child of " + c.parent->getName() + ", " + std::to_string(c.parent->getAge())) : "orphan");
}
Это вывод, который я получаю:
Я предполагаю, что мой вопрос все еще остается, может ли кто-нибудь дать мне пример того, как это должно выглядеть, чтобы считаться хорошим C ++?
@ user4581301 если вы посмотрите на обновленную главную страницу, значит, я должен вернуть std::unique_ptr
вместо * (raw pointer)
? В этом случае моя функция будет выглядеть так:
std::unique_ptr<Person> makePerson2()
{
string name;
int age;
cout << "Input name: ";
cin >> name;
cout << "Input age: ";
cin >> age;
return std::unique_ptr<Person>(new Person(name, age));
}
И объявление переменной как:
std::unique_ptr<Person> upParent = makePerson2();
cout << "printing: " << *upParent << endl;
Будет ли это считаться «лучшим» C ++, чем то, что я имею до сих пор?
Я думаю, что одна из самых важных вещей, которые нужно знать о C ++ особенно если вы пришли из Java / C # фона является:
По умолчанию объекты являются типами значений, а не ссылочными типами!
весь ваш код мог бы быть написан просто:
int main()
{
//this work
Person person("John Doe", 22);
//this work
Child child("Johnny Doe", 2, person);
cout << "---end of main---" << endl;
return 0;
}
Видите, как код превратился в ничто? Вам не нужно беспокоиться о распределении, удалении неиспользуемого объекта и т. д., потому что объекты не являются ссылочными типами для начала!
Моя личная иерархия правил:
std::unique_ptr
если по какой-то причине вам нужно динамическое распределение памяти, например, динамический полиморфизм. имейте в виду, что C ++ в основном работает с шаблонами как статический полиморфизм, а не с техникой наследования + переопределения в стиле Java. std::shared_ptr
редко, и только когда вы уверены, что есть много владельцев, например, объект, на который ссылаются в разных потоках. std::shared_ptr
следует использовать в крайних случаях. всегда копируйте общий указатель. никогда не передавайте общий указатель по ссылке.Тем не мение, new
, new[]
, delete
а также delete[]
в значительной степени устарели. только авторы библиотеки должны использовать их в самых крайних случаях. std::make_
должен быть единственным способом размещения объекта в куче, наряду с контейнерами STL, такими как std::vector
а также std::list
,
Других решений пока нет …