Подтвердить что ты не робот

Есть ли способ сделать передачу по ссылке и передать значение явно в вызове функции?

Если вы посмотрите на этот код,

int x = 0;
function(x);
std::cout << x << '\n';

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

Вот простой пример того, как я считаю, что это может быть проблемой:

std::string Lowercase(std::string str); //<- this is hidden away in code; probably in a different file.

int main(){
    std::string str = "HELLO";
    Lowercase(str);
    std::cout << str << '\n'; //<- Bug! we expected to output "hello".  The problem is not very easy to spot, especially when a function name sounds as though it will change the passed in value.
}

Чтобы избежать необходимости переходить между вызовом функции и объявлением функции (или, в некоторых случаях, документацией), чтобы понять поведение функции, есть способ явно документировать синтаксис вызова функции, который параметр, как ожидается, изменится (т.е. опорный параметр) или что копия отправляется (т.е. передается по значению)?

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


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

Я заметил, что некоторые люди пишут две подобные функции. Один из них принимает параметр значения, другой - указатель. Это позволяет вызывать такую ​​функцию:

Lowercase(str); //we assume the value will not change
Lowercase(&str); //we assume the value will change

Но это решение имеет много других проблем, и я не хотел бы терять пользу ссылок. Кроме того, мы по-прежнему делаем предположения о поведении.

4b9b3361

Ответ 1

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

Lowercase(&str);

... и Lowercase(), очевидно, было бы реализовано для ввода указателя. Такой подход может удовлетворить ваши потребности.

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

inplace_lowercase(str);

в значительной степени говорит, что он собирается делать. Очевидно, что inplace_lowercase() на самом деле был бы алгоритмом и с малой магией можно было бы разумно назвать

inplace_lowercase(str.begin() + 1, str.end());

.

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

  • Указатели могут быть пустыми. Обязательные контрольные параметры должны, на мой взгляд, быть ссылкой.
  • Передача указателем по-прежнему не указывает, может ли аргумент быть изменен, так как аргумент не может быть T const*.
  • Наличие значимых имен позволяет на самом деле легче понять, что происходит в первую очередь.
  • Вызов чего-либо без консультации с его документацией и/или зная, что вызываемая функция будет работать, не работает в любом случае и указывает, как все прошло, пытается вылечить симптомы более глубокой проблемы.

Ответ 2

Я не уверен, что полностью понимаю ваши требования, но, возможно, это то, что вы можете использовать:

template<typename T>
void foo( T ) { static_assert( sizeof(T)==0, "foo() requires a std::ref" ); }

void foo( std::reference_wrapper<int> t )
{
    // modify i here via t.get() or other means of std::reference_wrapper
}

int main()
{
    int i = 42;
    // foo( i ); // does not compile, static_assert fires
    foo( std::ref( i ) ); // explicit std::ref visible on the caller side
}

Ответ 3

Многие (большинство) IDE помогают вам справиться с этой проблемой, показывая прототип функции/метода, когда они выяснят, какую функцию вы вызываете.

Ответ 4

Это С++: отсутствие параметров in и out не означает, что язык недостаточен, это означает, что вам нужно реализовать то, что другие языки будут использовать в качестве языковой функции в качестве библиотеки.

Создайте два класса и функции template.

in_param<T> является оберткой вокруг T const&, а io_param<T> является оберткой вокруг ссылки T&. Вы создаете их, вызывая вспомогательные функции in и io.

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

Снаружи вызывающий должен вызывать in или io в аргументе, отмечая его на сайте вызова.

out сложнее: внутри дымки только назначение является законным. В идеале мы бы даже не построили его: может помочь метод emplace.

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

Что бы я сделал, это out_param имеет только operator=, и он присваивает. out обертывает что-то в out_param. Если вы хотите отложить конструкцию, используйте optional внутри параметра out, который приближается. Может быть, out_param также имеет emplace, который обычно просто присваивает, но если обернутый tyoe имеет emplace вместо этого вызывает его?

template<typename T>
struct in_param : std::reference_wrapper<T const> {
  explicit in_param( T const& t ):std::reference_wrapper<T const>(t) {}
  in_param( in_param<T>&& o ):std::reference_wrapper<T const>(std::move(o)) {}
  void operator=( in_param<T> const& o ) = delete;
};
template<typename T>
struct io_param : std::reference_wrapper<T> {
  explicit io_param( T& t ):std::reference_wrapper<T>(t) {}
  io_param( io_param<T>&& o ):std::reference_wrapper<T>(std::move(o)) {}
};
template<typename T>
in_param< T > in( T const& t ) { return in_param<T>(t); }
template<typename T>
io_param< T > io( T& t ) { return io_param<T>(t); }

template<typename T>
struct out_param {
private:
  T& t;
public:
  out_param( T& t_ ):t(t_) {}
  out_param( out_param<T>&& o ):t(o.t) {}
  void operator=( out_param<T> const& o ) = delete;
  void operator=( out_param<T> && o ) = delete;
  void operator=( out_param<T> & o ) = delete;
  void operator=( out_param<T> && o ) = delete;
  template<typename U>
  out_param<T>& operator=( U&& u ) {
    t = std::forward<U>(u);
    return *this;
  }
  // to improve, test if `t` has an `emplace` method.  If it does not,
  // instead do t = T( std::forward<Us>(us)... ). (I'd use tag dispatching
  // to call one of two methods)
  template<typename... Us>
  void emplace( Us&&... us ) {
    t.emplace( std::forward<Us>(us)... );
  }
};
template<typename T>
out_param<T> out( T& t ) { return out_param<T>(t); }

или что-то вроде выше.

Теперь вы получите синтаксис как:

void do_stuff( int x, in_param<expensive> y, io_param<something> z, out_param<double> d );

int main() {
  expensive a;
  something b;
  double d;
  do_stuff( 7, in(a), io(b), out(d) );
}

и отказ вызова in, io или out на сайте вызова приводит к ошибкам времени компиляции. Кроме того, out_param затрудняет случайное считывание состояния переменной out внутри функции, создавая очень приятную документацию на сайте вызова.

Ответ 5

Если вы используете MS VС++, то, возможно, это будет полезная информация об языке комментариев к исходному коду (SAL) http://msdn.microsoft.com/ru-ru/library/hh916383.aspx

Ответ 6

Я думаю, что это бесполезно уведомлять (по-русски [1]). Единственный необходимый вопрос: "Является ли мой объект семантически изменен?", И так:

  • Когда вы читаете прототип, вы знаете, может ли функция изменять объект (не const const) или нет (copy или const ref).
  • Когда вы используете функцию (даже не прочитав прототип [2]), если вы должны быть уверены, что не изменяете объект, используйте const_cast.

[1] Статический анализатор может сделать это для своих целей.
[2] Если вы пропустите, компилятор все равно предупредит вас.

Ответ 7

В этом весь смысл передачи по ссылке - синтаксически не нужно ничего делать, кроме передачи по значению.