Значение по умолчанию для параметра при передаче по ссылке в C ++


118

Можно ли задать значение по умолчанию для параметра функции, когда мы передаем параметр по ссылке. в C ++

Например, когда я пытаюсь объявить такую ​​функцию, как:

virtual const ULONG Write(ULONG &State = 0, bool sequence = true);

Когда я это делаю, выдает ошибку:

ошибка C2440: 'аргумент по умолчанию': невозможно преобразовать из 'const int' в 'unsigned long &' Ссылка, которая не является 'const', не может быть привязана к значению, отличному от lvalue


96
К счастью, мы не связаны руководством по стилю Google.

23
«Не делайте этого. Руководство по стилю Google (и другие) запрещает передачу неконстантных данных по ссылке» Я думаю, что руководства по стилю, как известно, содержат много субъективных частей. Это похоже на один из них.
Йоханнес Шауб - лит,

13
WxWidgets style guide says "don't use templates" and they have good reasons<- Screw std :: vector, я говорю
Johannes Schaub - litb

7
@jeffamaphone: Google Style Guide также запрещает использовать потоки. Вы тоже предлагаете избегать их?
rlbond

10
Молоток - ужасный инструмент для размещения отвертки, но его вполне можно использовать с гвоздями. Тот факт, что вы можете использовать некорректно, должен только предупредить вас о возможных подводных камнях, но не запретить его использование.
Дэвид Родригес - dribeas

Ответы:


104

Вы можете сделать это для константной ссылки, но не для неконстантной. Это связано с тем, что C ++ не позволяет привязать временную (в данном случае значение по умолчанию) к неконстантной ссылке.

Один из способов обойти это - использовать фактический экземпляр по умолчанию:

static int AVAL = 1;

void f( int & x = AVAL ) {
   // stuff
} 

int main() {
     f();       // equivalent to f(AVAL);
}

но это имеет очень ограниченное практическое применение.


если я сделаю его константным, это позволит мне передать другой адрес функции? или адрес State всегда будет 0 и будет бессмысленным?

Если вы используете ссылки, вы не передаете адреса.

3
boost :: array спешит на помощь void f (int & x = boost :: array <int, 1> () [0]) {..} :)
Йоханнес Шауб - litb

1
если не адреса, что на самом деле происходит?

2
@Sony Ссылка. Его трудно воспринимать как адрес. Если вам нужен адрес, используйте указатель.

34

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

virtual const ULONG Write(ULONG &State, bool sequence);
inline const ULONG Write()
{
  ULONG state;
  bool sequence = true;
  Write (state, sequence);
}

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

class A {}; 
class B {}; 
class C {};

void foo (A const &, B const &, C const &);
void foo (B const &, C const &); // A defaulted
void foo (A const &, C const &); // B defaulted
void foo (C const &); // A & B defaulted etc...

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

class Base {
public:
  virtual void f1 (int i = 0);  // default '0'

  virtual void f2 (int);
  inline void f2 () {
    f2(0);                      // equivalent to default of '0'
  }
};

class Derived : public Base{
public:
  virtual void f1 (int i = 10);  // default '10'

  using Base::f2;
  virtual void f2 (int);
};

void bar ()
{
  Derived d;
  Base & b (d);
  d.f1 ();   // '10' used
  b.f1 ();   // '0' used

  d.f2 ();   // f1(int) called with '0' 
  b.f2 ();   // f1(int) called with '0
}

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


19
Некоторые люди думают, что параметры по умолчанию менее ужасны, чем гигантское увеличение перегрузок.
Mr. Boy

2
Может быть, в конце вы скажете: d.f2(); // f2(int) called with '0' b.f2(); // f2(int) called with '0'
Пьетро

4
В последнем заявлении: начиная с C ++ 11 и далее, вы можете использовать конструкторы делегирования для вызова одного конструктора из другого, чтобы избежать дублирования кода.
Fred Schoen

25

По-прежнему существует старый способ C предоставлять необязательные аргументы: указатель, который может иметь значение NULL, если он отсутствует:

void write( int *optional = 0 ) {
    if (optional) *optional = 5;
}

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

11

Этот небольшой шаблон поможет вам:

template<typename T> class ByRef {
public:
    ByRef() {
    }

    ByRef(const T value) : mValue(value) {
    }

    operator T&() const {
        return((T&)mValue);
    }

private:
    T mValue;
};

Тогда вы сможете:

virtual const ULONG Write(ULONG &State = ByRef<ULONG>(0), bool sequence = true);

Где инстанциация ByReflive по памяти? Разве это не временный объект, который будет уничтожен при выходе из некоторой области (например, конструктора)?
Эндрю Чеонг

1
@AndrewCheong Его цель - построить на месте и уничтожить, когда линия будет завершена. Это средство предоставления ссылки на время вызова, чтобы параметр по умолчанию мог быть предоставлен, даже если он ожидает ссылку. Этот код используется в активном проекте и работает должным образом.
Майк Вейр,

7

Нет, это невозможно.

Передача по ссылке подразумевает, что функция может изменить значение параметра. Если параметр не предоставляется вызывающей стороной и исходит из константы по умолчанию, какую функцию предполагается изменить?


3
Традиционным способом FORTRAN было бы изменить значение 0, но этого не происходит в C ++.
Дэвид Торнли,

7

Есть две причины для передачи аргумента по ссылке: (1) для производительности (в этом случае вы хотите передать по ссылке const) и (2), потому что вам нужна возможность изменять значение аргумента внутри функции.

Я очень сомневаюсь, что использование unsigned long на современных архитектурах слишком сильно замедляет вас. Итак, я предполагаю, что вы собираетесь изменить значение Stateвнутри метода. Компилятор жалуется, потому что константа 0не может быть изменена, так как это rvalue («не-lvalue» в сообщении об ошибке) и неизменяемое ( constв сообщении об ошибке).

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

Другими словами, не constссылки должны относиться к фактическим переменным. Значение по умолчанию в функции signature ( 0) не является реальной переменной. У вас та же проблема, что и:

struct Foo {
    virtual ULONG Write(ULONG& State, bool sequence = true);
};

Foo f;
ULONG s = 5;
f.Write(s); // perfectly OK, because s is a real variable
f.Write(0); // compiler error, 0 is not a real variable
            // if the value of 0 were changed in the function,
            // I would have no way to refer to the new value

Если вы на самом деле не собираетесь вносить изменения Stateв метод, вы можете просто изменить его на const ULONG&. Но вы не получите от этого большого выигрыша в производительности, поэтому я бы порекомендовал изменить его на нереференсный ULONG. Я заметил, что вы уже возвращаете a ULONG, и у меня есть скрытое подозрение, что его значением является значение Stateпосле любых необходимых изменений. В этом случае я бы просто объявил метод так:

// returns value of State
virtual ULONG Write(ULONG State = 0, bool sequence = true);

Конечно, я не совсем понимаю, что вы пишете и куда. Но это другой вопрос в другой раз.


6

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

int* foo (int& i )
{
   return &i;
}

foo(0); // compiler error.

const int* bar ( const int& i )
{
   return &i;
}

bar(0); // ok.

Убедитесь, что у вашего значения по умолчанию есть адрес, и все в порядке.

int null_object = 0;

int Write(int &state = null_object, bool sequence = true)
{
   if( &state == &null_object )
   {
      // called with default paramter
      return sequence? 1: rand();
   }
   else
   {
      // called with user parameter
      state += sequence? 1: rand();
      return state;
   }
}

Я использовал этот шаблон несколько раз, когда у меня был параметр, который мог быть переменной или нулем. Обычный подход заключается в том, чтобы пользователь передавал указатель в этом случае. Они передают NULL-указатель, если не хотят, чтобы вы вводили значение. Мне нравится подход с нулевым объектом. Это облегчает жизнь вызывающим абонентам, не сильно усложняя код вызываемого абонента.


ИМХО, этот стиль довольно "вонючий". Единственный случай, когда аргумент по умолчанию действительно оправдан, - это когда он используется в конструкторе. Во всех остальных случаях перегрузки функций обеспечивают точно такую ​​же семантику без каких-либо других проблем, связанных со значениями по умолчанию.
Ричард Корден,

1

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


2
Значения по умолчанию не «оцениваются как константы».

1

Другой способ мог быть следующим:

virtual const ULONG Write(ULONG &State, bool sequence = true);

// wrapper
const ULONG Write(bool sequence = true)
{
   ULONG dummy;
   return Write(dummy, sequence);
}

то возможны следующие вызовы:

ULONG State;
object->Write(State, false); // sequence is false, "returns" State
object->Write(State); // assumes sequence = true, "returns" State
object->Write(false); // sequence is false, no "return"
object->Write(); // assumes sequence = true, no "return"

1
void f(const double& v = *(double*) NULL)
{
  if (&v == NULL)
    cout << "default" << endl;
  else
    cout << "other " << v << endl;
}

Оно работает. По сути, это доступ к ссылке на значение для проверки ссылки, указывающей NULL (логически нет ссылки NULL, только то, что вы указываете, является NULL). Сверху, если вы используете какую-то библиотеку, которая работает со «ссылками», то обычно будут некоторые API, такие как «isNull ()», которые делают то же самое для конкретных ссылочных переменных библиотеки. И предлагается использовать эти API в таких случаях.
parasrish

1

В случае OO ... Сказать, что данный класс имеет и "Default" означает, что это значение по умолчанию (значение) должно быть объявлено, соответственно, и затем может быть использовано как параметр по умолчанию, например:

class Pagination {
public:
    int currentPage;
    //...
    Pagination() {
        currentPage = 1;
        //...
    }
    // your Default Pagination
    static Pagination& Default() {
        static Pagination pag;
        return pag;
    }
};

О вашем методе ...

 shared_ptr<vector<Auditoria> > 
 findByFilter(Auditoria& audit, Pagination& pagination = Pagination::Default() ) {

Это решение вполне подходит, поскольку в этом случае «Глобальная разбивка на страницы по умолчанию» является единственным «ссылочным» значением. У вас также будет возможность изменять значения по умолчанию во время выполнения, например, конфигурация «глобального уровня», например: пользовательские настройки навигации по страницам и т. Д.


1

Это возможно с квалификатором const для State:

virtual const ULONG Write(const ULONG &State = 0, bool sequence = true);

Бессмысленно и даже нелепо передавать long как const ref и не достигает того, чего хочет OP.
Джим Балтер


0

Для этого тоже есть довольно подвох:

virtual const ULONG Write(ULONG &&State = 0, bool sequence = true);

В этом случае вы должны вызвать его с помощью std::move:

ULONG val = 0;
Write(std::move(val));

Это всего лишь забавный обходной путь, я категорически не рекомендую использовать его в реальном коде!


0

У меня есть обходной путь, см. Следующий пример значения по умолчанию для int&:

class Helper
{
public:
    int x;
    operator int&() { return x; }
};

// How to use it:
void foo(int &x = Helper())
{

}

Вы можете сделать это для любого тривиального типа данных, например bool, double...


0

Определите 2 функции перегрузки.

virtual const ULONG Write(ULONG &State, bool sequence = true);

virtual const ULONG Write(bool sequence = true)
{
    int State = 0;
    return Write(State, sequence);
}

-3

виртуальная константа ULONG Write (ULONG & State = 0, bool sequence = true);

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

virtual const ULONG Write(ULONG &State = *(ULONG*)0, bool sequence =
> true);

3
Разыменование указателя NULL недопустимо. В некоторых случаях это может сработать, но это незаконно. Подробнее читайте здесь parashift.com/c++-faq-lite/references.html#faq-8.7
Spo1ler,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.