Значення за замовчуванням для параметра при проходженні посилань у C ++


117

Чи можна надати параметр функції за замовчуванням, коли ми передаємо параметр посиланням. в C ++

Наприклад, коли я намагаюся оголосити функцію типу:

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

Коли я це роблю, це видає помилку:

помилка C2440: "аргумент за замовчуванням": не вдається перетворити з "const int" в "непідписаний довгий".


96
На щастя, нас не пов'язує посібник зі стилів Google.

23
"Не робіть цього. Посібник зі стилів Google (та інші) забороняє непростувати пропуск за посиланням" Я думаю, що посібники зі стилів містять багато суб'єктивних частин. Це схоже на одну з них.
Йоханнес Шауб - ліб

13
WxWidgets style guide says "don't use templates" and they have good reasons<- гвинт std :: вектор, я кажу
Йоганнес Шауб - litb

7
@jeffamaphone: Посібник зі стилів Google також говорить, що не використовувати потоки. Чи запропонуєте ви їх також уникати?
rlbond

10
Молоток - це жахливий інструмент для розміщення викрутки, але він цілком придатний для використання з цвяхами. Той факт, що ви можете зловживати функцією, повинен лише попереджати про можливі підводні камені, але не забороняти його використання.
Девід Родрігес - дрибес

Відповіді:


103

Ви можете зробити це для посилання на const, але не для non-const. Це пояснюється тим, що C ++ не дозволяє тимчасовому (значення за замовчуванням у цьому випадку) прив'язуватися до посилань без const.

Одним із способів цього є використання фактичного екземпляра за замовчуванням:

static int AVAL = 1;

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

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

але це дуже обмежене практичне використання.


якщо я змушу це const, чи дозволить мені передати іншу адресу функції? чи адреса держави завжди буде 0 і так безглузда?

Якщо ви використовуєте посилання, ви не передаєте адреси.

3
boost :: масив до рятувальної void f (int & x = boost :: array <int, 1> () [0]) {..} :)
Йоханнес Шауб - ліб

1
якщо не адреси, що насправді передається?

2
@Sony Посилання. Неправильно вважати це адресою. Якщо ви хочете адресу, скористайтеся вказівником.

33

Про це було сказано в одному з прямих коментарів до вашої відповіді, але лише для того, щоб заявити про це офіційно. Що ви хочете використовувати, це перевантаження:

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
Деякі люди вважають, що параметр за замовчуванням менш жахливий, ніж гігантське множення перевантажень.
Пан Хлопчик

2
Можливо, наприкінці, ви нагадаєте: d.f2(); // f2(int) called with '0' b.f2(); // f2(int) called with '0'
П'єтро

4
Останнє твердження: починаючи з C ++ 11, ви можете використовувати делегуючі конструктори для виклику одного конструктора від іншого, щоб уникнути дублювання коду.
Фред Шоен

25

Ще існує старий спосіб надання необов'язкових аргументів: вказівник, який може бути 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);

Де береться інстанція ByRefживого, пам’ятного? Хіба це не тимчасовий об’єкт, який може бути знищений, залишивши деякий обсяг (наприклад, конструктор)?
Ендрю Чонг

1
@AndrewCheong Вся його мета полягає у побудові на місці та знищенні, коли лінія завершена. Це засіб виявлення посилання на час виклику, щоб параметр за замовчуванням міг бути наданий навіть тоді, коли він очікує посилання. Цей код використовується в активному проекті та функціонує, як очікувалося.
Майк Вейр

7

Ні, це неможливо.

Проходження посилань означає, що функція може змінити значення параметра. Якщо параметр не надається абонентом і походить від константи за замовчуванням, то яку функцію слід змінити?


3
Традиційним способом FORTRAN було б змінити значення 0, але це не відбувається в C ++.
Девід Торнлі

7

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

Я дуже сумніваюся, що проходження довго не підписаних сучасних архітектур занадто сповільнює вас. Тому я припускаю, що ви маєте намір змінити значення Stateвсередині методу. Компілятор скаржиться на те, що константа 0не може бути змінена, оскільки це значення rvalue ("не-значення" у повідомленні про помилку) та незмінне ( constу повідомленні про помилку).

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

Інакше кажучи, не constпосилання мають посилатися на фактичні змінні. Значення за замовчуванням у підписі функції ( 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. Я помічаю, що ви вже повертаєте 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 в таких випадках.
парашутувати

1

У випадку OO ... Якщо сказати, що для даного класу є "та" за замовчуванням ", це означає, що це значення за замовчуванням (значення) повинно бути оголошеним відповідно, тоді може бути usd як параметр за замовчуванням, наприклад:

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 для штату:

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

Безглуздо і навіть смішно проходити довго як конст-реф, і не досягати того, чого хоче ОП.
Джим Балтер


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

віртуальний const ULONG Write (ULONG & State = 0, послідовність bool = вірно);

Відповідь досить проста, і я не так добре в поясненні, але якщо ви хочете передати значення за замовчуванням параметру non-const, який, ймовірно, буде змінено в цій функції, це використовувати його так:

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.