Путаница в C ++ - PullRequest
       31

Путаница в C ++

5 голосов
/ 26 июня 2010

Я очень плохо знаком с C ++ и сейчас изучаю его.У меня есть несколько вопросов ..

  1. В чем разница между void DoSomething (const Foo & foo) и void DoSomething (Foo foo)? Если мы не укажем &, тогда экземпляр Foo будет передан по значению (не по ссылке).Это будет то же самое, что и аргумент const + & в аргументе, за исключением проверки во время компиляции.Итак, почему наличие const + & становится лучшей практикой по сравнению с аргументами без & и const?

    В C # передача объекта осуществляется «по ссылке», но, похоже, его нет в C ++.

  2. В книге, которую я читаю, сказано, что функции-члены передают неявный параметр по ссылке.

    Может ли кто-нибудь дать мне образец неявного параметра и по ссылке?Я знаю, что если мы хотим передать объект по ссылке, нам нужно использовать & (например, Foo (Person & p)), но почему C ++ передает объект по ссылке для неявного параметра?Я читал, что неявный параметр в C ++ похож на Contructor (строка str): strMemberVariable (str) {} ...

  3. Является ли массив единственным, который передается по ссылке в C ++?

  4. Почему я не могу использовать Foo fInstance в классе Foo?

Пример:

class Foo {

public:    
    Foo() { }

    Foo(const Foo& f) : fInstance(f) {   }  

    Foo fInstance;      
};

Заранее спасибо.

Ответы [ 12 ]

10 голосов
/ 26 июня 2010

1 В чем разница между void DoSomething (const Foo & foo) и void DoSomething(Foo foo)?Если мы не укажем &, тогда экземпляр Foo будет передан по значению (не по ссылке).Это будет то же самое, что и аргумент const + & в аргументе, за исключением проверки во время компиляции.Итак, почему использование const + & становится лучшей практикой по сравнению с аргументами без & и const?

В C # передача объекта происходит «по ссылке», но, похоже, его нет в C ++.

Существует несколько различий в порядке важности:

  • Если объект Foo не может быть скопирован, его необходимо передать по ссылке
  • Если объект Foo является базовым классом, вы должны получить его по ссылке, чтобы пользователи могли вызывать ваши функции с производными классами
  • Значение фактического объекта может измениться, даже если вы удерживаете const ссылку на него
  • Эффективность, копирование пользовательских типов может быть дорогим, но компиляторы могут быть достаточно умными, чтобы понять это, так что ...

2 В книге, которую я читаю, сказано, что членфункции передают неявный параметр по ссылке ..

Может кто-нибудь дать мне образец неявного параметра и по ссылке?Я знаю, что если мы хотим передать объект по ссылке, нам нужно использовать & (например, Foo (Person & p)), но почему C ++ передает объект по ссылке для неявного параметра?Я читал, что неявный параметр в C ++ похож на Contructor (строка str): strMemberVariable (str) {} ...

Под неявным параметром следует понимать this, то есть сам объект.Он эффективно передается по ссылке, поскольку вы можете изменить его состояние в функции-члене.

После замечания Konrad: обратите внимание, что this само по себе не передается по ссылке, this является ссылкой (указатель) на объект, но передается по значению.Вы не можете изменить адрес памяти вашего объекта так, как вам хочется;)

3 Является ли массив единственным, который передается по ссылке в C ++?

Они не 'т.Вы увидите изменения в элементах массива, но массив (структура) не изменится.

После замечания FredOverflow, иллюстрация:

void fun(int* p, size_t size);

int main(int argc, char* argv[])
{
  int array[15];
  fun(array, 15);
}

Мы неНе зная, что делает fun, он, вероятно, изменит некоторые элементы array, но независимо от его действия, array останется массивом из 15 целых чисел: содержимое меняется, а структура - нет.

В результате, чтобы изменить array, нам нужно другое объявление:

void changer(int*& array, size_t& size);

Таким образом, мы можем изменить как содержимое, так и структуру (и также вернуть новый размер).И, конечно, мы можем вызывать эту функцию только с массивом, который был выделен динамически.

4 Почему я не могу использовать Foo fInstance в классе Foo?

Потому что этобесконечная рекурсия.Подумайте об этом с точки зрения компилятора и попытайтесь угадать размер Foo.Размер Foo является суммой размеров его атрибутов плюс, возможно, некоторая информация о заполнении и типе.Кроме того, размер объекта составляет не менее 1, чтобы к нему можно было обратиться.Итак, если Foo имеет Foo, каков его размер :)?

Обычным решением является использование умного указателя:

class Foo
{
public:

private:
  std::unique_ptr<Foo> mInstance;
};

Поскольку размер указателя делаетне зависит от размера объекта, на который указывает объект, поэтому здесь не происходит рекурсия:)

7 голосов
/ 26 июня 2010

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

Какая разница между void DoSomething(const Foo& foo) и void DoSomething(Foo foo)?

Как уже говорили другие, для второго кода требуется копия (обычно вызывая конструктор копирования Foo).

Итак, почему наличие const + & становится лучшей практикой по сравнению с аргументами без & и const?

Есть несколько особых целей, на которые другие уже ответили (например, полиморфизм во время выполнения). Это не объясняет, почему это стало лучшей практикой. Причина этого проста и безобразна: потому что она величин более эффективна. Представьте себе передачу вектора или строки другому методу - или просто любой большой структуре данных. Стоимость копирования обычно будет огромной, и методы могут часто вызываться в коде - на самом деле методы обычно вызываются очень часто, в противном случае код плохо спроектирован.

С другой стороны, когда вы передаете объект как ссылку const, это внутренне (обычно) реализуется через указатель. Указатели можно всегда эффективно копировать на всех архитектурах.

В книге, которую я читаю, сказано, что функции-члены передают неявный параметр по ссылке.

Я думаю, что книга не права. Функции-члены классов неявно получают указатель this, который ссылается на текущий объект. Однако это указатель , и C ++ запрещает его изменение. Нет причин, по которым он будет передан по ссылке.

Является ли массив единственным, который передается по ссылке в C ++?

Массивы вообще редко передаются в C ++ - они обычно передаются в виде указателей:

void foo(int[] x) { … }

фактически совпадает с

void foo(int* x) { … }

Компилятор обрабатывает эти два объявления одинаково. Когда вы пытаетесь вызвать любой из этих методов и передать ему массив x, C ++ неявно преобразует массив в указатель на его первый элемент - это называется «decay». Итак, foo(x) станет foo(&x[0]).

Однако массивы могут вместо этого передаваться по ссылке, если задан их размер:

void foo(int (&x)[4]);

Но еще раз, вы явно объявляете, что массив будет передан по ссылке.

4 голосов
/ 26 июня 2010

Почему я не могу использовать Foo fInstance в классе Foo?

Потому что концептуально объекту Foo потребуется бесконечное количество пространства. Технически, тип Foo является неполным в определении Foo.

То, что вам, вероятно, нужно, это указатель на Foo как члена.

4 голосов
/ 26 июня 2010

В C # передача объекта осуществляется «по ссылке», но, похоже, его нет в C ++.

Нет, это неправильно, это распространенное заблуждение. В таких языках, как C #, VB и Java, переменные всегда передаются по значению (исключение явно передается как ref в C # или ByRef в VB).

Отличие от C ++ состоит в том, что переменные не содержат сам объект класса, они содержат только ссылку. Таким образом, то, что передается методу, это не сам объект, а только его ссылка (но , что передается по значению).

Разница довольно важная. Если в C # используется передача по ссылке, следующий код выведет другой результат:

void foo(string s) {
    s = "world";
}

string s = "hello";
foo(s);
Console.WriteLine(s); // prints "hello"
1 голос
/ 26 июня 2010

По одному:

  1. doStuff(Foo f) означает, что новый объект Foo будет создан в стеке при вызове метода - AKA by-value .Вызов doStuff(const Foo &f) означает, что вы просто передаете новую ссылку, объект не дублируется, вы только держите ссылку на него.Это самый безопасный способ передачи аргументов, поскольку он не предусматривает дублирование копии объекта.Это называется передачей по ссылке и является наиболее близким к поведению Java / C #.

  2. О каком неявном параметре вы говорите?

  3. Опять же, массивы (при условии, что они std::array с) могут передаваться по значению, указателю или ссылке - единого поведения не существует.Как упоминал Конард, массивы в стиле C (не более чем блоки памяти) не могут быть переданы по значению .

1 голос
/ 26 июня 2010

Разница между void DoSomething(const Foo& foo) и void DoSomething(Foo foo) заключается в том, что первый передает параметр по ссылке, а второй по значению. Практические отличия:

  1. Эффективность. Передача по значению может потребовать вызова конструктора копирования. Если конструктор копирования дорогой, передача по значению приведет к дополнительным издержкам.
  2. Применимость. Для передачи по значению требуется открытый конструктор копирования. Если класс не поддерживает конструктор копирования, он не может быть передан по значению.
  3. Семантика. При передаче по ссылке вы не знаете, на кого может ссылаться объект. Если базовый объект изменяется по какой-либо другой причине, значение ссылки изменится.

Чтобы объяснить # 3 немного лучше, рассмотрим следующую ситуацию:

std::string global_string;

void foo(const std::string &str)
{
    if (str.empty())
    {
        global_string = "whatever";
        // is str still empty??
    }
}

Если foo называется foo(global_string), то при изменении global_string это также меняет str.

0 голосов
/ 27 июня 2010

Какая разница между пустотой DoSomething (const Foo & Foo) и пустота DoSomething (Foo Foo)?

Вообще говоря, последний будет глубокая копия передаваемый аргумент (другими словами, он создает копию исходного объекта Foo). Первый из них создаст поверхностную копию передаваемого аргумента (копируя его адрес в неизменяемую ссылку const, а не копируя фактический объект Foo).

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

Может ли кто-нибудь дать мне образец неявного параметра и по ссылке? Я знаю, что если мы хотим передать объект по ссылке, нам нужно использовать & (например, Foo (Person & p)), но почему C ++ передает объект по ссылке для неявного параметра? Я прочитал, что неявный параметр в C ++ похож на Contructor (строка str): strMemberVariable (str) {} ...

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

class Foo
{
    Foo(int x) {...}
};

Это неявно. Это позволяет нам писать код вроде:

Foo foo = 123;

void f(const Foo& x);
f(123);

Пока это явно:

class Foo
{
    explicit Foo(int x) {...}
};

... и не будет предыдущего кода. Предыдущий код должен быть изменен соответственно:

Foo foo(123);

void f(const Foo& x);
f(Foo(123) );

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

Является ли массив единственным, что проходит мимо ссылка на C ++?

Я не совсем уверен, о чем здесь говорят, но массивы нельзя передавать по значению, если вы это имеете в виду. Мы можем только передавать ссылки / указатели на массивы:

// takes an array of 10 integers
void fn(int(&some_array)[10]);

// takes a pointer to an int array
void fn(int* some_array);

// takes a pointer to an int array (the 10 
// literal constant is ignored) and this function
// can likewise take any pointer to int
void fn(int some_array[10]);

Почему я не могу использовать Foo fInstance в Foo класс

Это бесконечно рекурсивно. Foo хранит инстанс, fInstance - другой инстанс и так далее. Нет ничего, что могло бы остановить рекурсию, поэтому у вас будут только объекты, хранящие объекты, хранящие объекты, хранящие объекты и так далее, пока у вас не закончится память. Таким образом, компиляторы обнаруживают это условие и не допускают его, поскольку от него не может произойти законное поведение во время выполнения. Также не было бы способа определить размер Foo - это было бы бесконечное значение.

0 голосов
/ 26 июня 2010
  What is the differences between void DoSomething(const Foo& foo) and

void DoSomething (Foo foo)?

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

0 голосов
/ 26 июня 2010

Это не совсем принятая "хорошая практика" - передавать по константной ссылке, а не по значению.

В этом блоге объясняется, почему.Ссылка const быстрее, но правда в том, что компилятору разрешено оптимизировать удаление копии при передаче по значению, поэтому передача по значению является хорошим значением по умолчанию (и действительно, стандартная библиотека обычно делает это. Например, std::for_each принимаетдва итератора по значению и один функтор по значению )

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

Многие объекты представляют собой то, что не может или не должно копироваться.Как правило, они имеют собственный конструктор копирования и должны быть переданы по ссылке или константной ссылке на функции.

Другая причина для передачи по ссылке (константная или иная) может заключаться в использовании полиморфных объектов.Допустим, у вас есть базовый класс B и производный класс D.Вы можете безопасно передать объект типа D как const B&, но передача его по значению как объекта типа B может привести к появлению срезов (копируется только подобъект B вместо всего объекта D).

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

0 голосов
/ 26 июня 2010

В книге, которую я читаю, сказано, что функции-члены передают неявный параметр по ссылке.

В книге говорится о неявном указателе this , которыйпередается каждой нестатической функции-члену, определенной в классе.Это потому, что C ++ содержит копию каждой функции-члена в классе, а не в каждом объекте, поэтому метод должен знать, с каким объектом этого класса он должен работать.

class FOO{
 int x;
 void doSomthing(int x);
}

void FOO::doSomething(int x){
  x = x;
}

будет скомпилировано во что-то подобное

void FOO::doSomething(FOO* this, int x){
  this->x = x;
}

Поскольку статические функции являются функциями класса, а не объектными функциями, им не нужно создавать объект для вызова, поэтому они не должны иметь доступа к нестатическим полям класса.и, следовательно, не нужен указатель this на объект.

...