От C # / Java к C ++: Понимание ссылок на C ++ в примере? - PullRequest
4 голосов
/ 26 октября 2011

Исходя из C # / Java, я пытаюсь понять лучшие практики использования указателей и ссылок C ++. Я уверен, что это было покрыто до тошноты на этом сайте, но я до сих пор не до конца понимаю. Я прочитал некоторые часто задаваемые вопросы по C ++, но мне нужно увидеть их в контексте. Предположим, у меня есть два класса:

class Employee
{
  Employee();
  ~Employee();
}


class Company
{
   Company();
   ~Company();

   AddEmployee(?? employee);
   ?? GetEmployee();

private:
   std::list<Employee??> employees_;
}

Где AddEmployee берет объект сотрудника и добавляет его в личный список сотрудников. Каким должен быть тип параметра AddEmployee employee?

AddEmployee(Employee *employee);
AddEmployee(Employee& employee);

Каким типом шаблона должен быть закрытый std :: list?

Также предполагается, что класс компании может изменить сотрудника. const уместно здесь? И если я использую ссылочный параметр, может ли класс Company развернуться и передать объект employee в другой класс?

UPDATE

Я вижу, что многие люди предлагают использовать семантику значений, но из C # / java это сложно. Обычно я делаю внедрение зависимостей с помощью интерфейсов, и я не уверен, что вы даже можете использовать семантику значений в этом случае. Можете ли вы кодировать интерфейсы, используя семантику значений?

В коде, над которым я сейчас работаю, почти все объекты размещены в куче, а мои функции-члены выглядят как void TakeSomeObject(SomeObject *someObject) или MyClass(SomeService *service). Код работает, хотя по мере усложнения кода меня также беспокоят утечки памяти. Часть того, что мне было интересно, стоит ли мне менять эти сигнатуры методов, чтобы вместо них использовать ссылки, и каковы последствия этого.

Также, когда я читал об умных указателях, я хотел просмотреть мой код и заменить большинство указателей на shared_ptr, но мне не ясно, хорошо это или нет. И в этом случае я просто изменил бы все мои методы, чтобы принять shared_ptr в качестве параметра?

ОБНОВЛЕНИЕ 2

Кажется, что консенсус заключается в том, чтобы использовать AddEmployee(const Employee& employee) или AddEmployee(Employee& employee) для сотрудников, выделенных в куче. Я открыт для перемен перед лицом убедительных доказательств, но я не вижу себя пишущим версию семантики значений прямо сейчас: AddEmployee(Employee employee)

ОБНОВЛЕНИЕ 3

Я пытался написать такой метод, как:

AddEmployee(const Employee& employee)
{
  employees_.push_back(??employee);
}

Однако, похоже, это не работает, пытаясь заменить ?? с ничего, &, или * когда список определен как std::list<Employee*> employees_. Означает ли это, что мне нужно взять параметр указателя, а не ссылочный параметр, чтобы сделать что-то подобное?

Ответы [ 5 ]

2 голосов
/ 26 октября 2011

Ответ на ваши первые две части (примерно AddEmployee и GetEmployee частично зависит от того, насколько сильно копирует Employee. Это копируется вообще? Если нет, то вы должны передать его по ссылке.

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

Теперь для AddEmployee действительно нет причин не принимать это значение как const&.скопировать его, чтобы поместить его в std::list (подробнее об этом позже), чтобы не было реальной проблемы с его использованием в качестве постоянной ссылки. Это может быть как const Employee & или Employee const &; выберите, они означаютТо же самое.

Для GetEmployee вы должны задать себе вопрос: что я хочу, чтобы пользователь делал с ним? Например, в некоторых случаях вы хотите, чтобы пользователь мог изменить фактическоеEmployee объект хранится в std::list. Теперь грантеd, я бы посчитал, что это немного подозрительно или, по крайней мере, заслуживает какой-либо формы объяснения того, почему вам нужно передать ваш фактический Employee объект кому-то для изменения, но довольно часто возвращают ссылку.* Если вы это сделаете, вы также должны предоставить const версию GetEmployee, которая возвращает const&.Вот так:

Employee &GetEmployee();
const Employee &GetEmployee() const;

Таким образом, если у них есть const Company, они могут получить const Employee, на который они могут смотреть, но не изменять .

Что касается std::list, это действительно просто: вы никогда не храните контейнер ссылок. НАВСЕГДА .Ваши варианты являются значениями или указателями.На самом деле ваши параметры должны быть значениями или умными указателями (посмотрите вверх).Так как мы не имеем дело с умными указателями здесь, мы должны просто использовать значения.

Так что я бы предложил это:

class Company
{
   Company();
   ~Company();

   AddEmployee(const Employee &employee);
   const Employee &GetEmployee() const;
   Employee &GetEmployee();

private:
   std::list<Employee> employees_;
}
2 голосов
/ 26 октября 2011

Каким должен быть тип параметра сотрудника AddEmployee?

Параметр должен иметь тип Employee const&, то есть постоянная ссылка на сотрудника. Это сделано для того, чтобы избежать копирования в качестве аргумента параметра и указать, что указанный объект не будет изменен внутри функции. Это, в свою очередь, позволяет вызывать функцию с временными значениями.

Каким типом шаблона должен быть закрытый std :: list?

Тип списка должен быть std::list< Employee >, и class Company будет владельцем таких Employee объектов (на самом деле list будет). Если вы смотрите на более сложные отношения владения, подобные тем, которые обычно встречаются в C #, вам лучше использовать контейнер интеллектуальных указателей (или контейнер указателей из Boost).

1 голос
/ 26 октября 2011

Это список Employee с:

std::list<Employee> employees_;

Вы могли бы иметь список указателей на Employee с, как это

std::list<Employee *> employees_;

но тогда вам придется управлять фактическими Employee s (вещами, на которые указывают указатели) в куче. Иногда хорошая идея, но в этом случае, вероятно, просто боль.

Это:

Employee GetEmployee();

возвращает Employee, значение, объект, предположительно, копию одной из вещей в списке. Вы могли бы иметь такую ​​функцию:

Employee & GetEmployee();
Employee * GetEmployee();

, которые возвращают ссылку или указатель (соответственно) на фактического сотрудника. Первое в этом случае плохая идея, потому что std::list не обещает сохранять его содержимое безопасным для ссылок; он может сделать копию одного из Employee s, сохранить его и удалить оригинал. У последнего та же проблема, но он немного безопаснее, если вы используете std::list<Employee *>, потому что список будет перетасовывать указатели, но оставит объекты в покое - но в этом случае нет хорошего способа справиться с ответственностью за кучу: имеет смысл для Company создать Employee s, поэтому для него будет хорошей идеей также удалить их, но тогда то, что называется GetEmployee, может закончиться с указателем несуществующей.

Любой из них подойдет:

AddEmployee(Employee employee);
AddEmployee(Employee & employee);

Второй вариант немного лучше, потому что он позволяет избежать ненужной копии. В любом случае, копия оригинала окажется в списке. Не используйте AddEmployee(Employee * employee); это совершенно законно, но вы просто добавляете возможность вызвать ошибку, передав ей недопустимый указатель.

1 голос
/ 26 октября 2011

Я считаю, что это дело вкуса, и что вы должны делать обе подписи.

Однако список сотрудников для большинства начинающих должен быть - но, очевидно, вопрос в том, какую функциональность вы пытаетесь реализовать, - это список реальных экземпляров, а не указателей, и он не может быть ссылками

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

class Company
{
   Company();
   ~Company();

   AddEmployee(const Employee& employee) { employee_.push(employee); }
   AddEmployee(const Employee* employee) { employee_.push(*employee); }
   const Employee& GetEmployee() const;
   Employee& GetEmployee();

private:
   std::list<Employee> employees_;
}

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

В целом, я бы избегал раздачи указателей на внутреннюю структуру данных, поэтому GetEmployee должен в лучшем случае разослать ссылку (которая также является своего рода указателем, но разным) - здесь вам может потребоваться доступ - один для const и один для неконстантных контекстов.

1 голос
/ 26 октября 2011

Каким должен быть тип параметра сотрудника AddEmployee?

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

// Example where you give up ownership
Employee* employee = new Employee();

// Call the method taking in a pointer to an employee
AddEmployee(employee);

// You allocated it, but you didn't delete it,
// so whoever owns employee now will have to delete it.
employee = NULL; 

Ты тоже можешь быть плохим по этому поводу:

// Example where you give up ownership
Employee* employee = NULL;

// Oops, you're passing in a null pointer.
AddEmployee(employee);

// Allocate an employee
employee = new Employee(); 
delete employee;

// Oops, you're passing in an deallocated object which is 
// the worst case scenario because the behavior is undefined.
AddEmployee(employee);

Взятие ссылки на объект Employee безопаснее, поскольку язык не позволяет передавать нулевую ссылку: другими словами, ссылка гарантированно будет распределена и не будет нулевой. Рекомендуется использовать ссылку вместо указателя, когда это имеет смысл.

AddEmployee(Employee& employee);

Каким типом шаблона должен быть закрытый std :: list?

Обновление: Я получил ваш вопрос сейчас, и ответ сложный: хотя для вашего конкретного случая, вероятно, было бы лучше иметь указатели на Employee, поскольку они, скорее всего, будут долгоживущими объектами и вы не хотите хранить их в стеке. Так что для вашего случая ответ list<Employee*>. Тем не менее, вот несколько вопросов, которые касаются многих нюансов, связанных с принятием решения, когда идти с выделенным стеком против выделенной кучи:


Также предположить, что класс компании может изменить сотрудника, здесь уместно?

Если вы используете ссылку и не копируете объект, вы не можете передать его как const и изменить его. Если вы используете указатель, вы попадаете в const правильность , и вы можете прочитать больше об этом здесь: http://www.parashift.com/c++-faq-lite/const-correctness.html


А если я использую ссылочный параметр, сможет ли класс Company развернуться и передать объект employee в другой класс?

Да.

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