Hacker News new | ask | show | jobs
by adrian_b 591 days ago
In C++ or similar languages, where the function parameters behave semantically as either "in" or "in out" parameters, a function that returns an object must have already created somehow the object.

Therefore calling a function just adds a task of invoking a constructor also into the function, it cannot replace a constructor.

In C++, an object cannot be created by assigning a value to it, because the left hand parameter of the assignment operator is an "in out" parameter, like for any other function, so it is assumed that it already has a value of the type of that object.

Therefore an assignment operator must execute the equivalent of invoking a destructor for the target of the assignment, followed by the invocation of a copy constructor.

Attempting to assign a value to memory allocated but not initialized will attempt to invoke a destructor for an invalid object value.

The constructors are the only C++ functions that implicitly have a result which is an "out" parameter, not an "in out" parameter, like the other functions.

Because the memory where the result of the constructor will be placed is not initialized, no destructor must be invoked for it, so all will be OK.

All these rules about when to use constructors and when to use functions and the need to have duplicate almost identical in meaning functions for certain purposes, e.g. copy constructors and assignment operators, complicates a lot C++.

Any copy or deep copy operations would be implemented very simply, just by copying what needs to be copied.

There would be no need for "move" operations of any kind, which are just a trick to avoid the inefficient code generated by compilers when the semantics for some parameters is "in out" when what would have really been needed is "out".

Specifying that some functions are not normal functions, but constructors, or that some operations have "move semantics" is just an extremely complicated way to specify that some function parameters are "out", not "in out". Besides being hard to understand, these tricks only work in particular cases, instead of being able to specify the mode of any function parameter.

In a language with distinction between "in out" and "out", the mode for the result of assignment is naturally "out" and there is no need for the contortions of C++. In such a language a compiler knows when to invoke destructors and when not to invoke them and it knows when no extra temporaries are needed for computing a complex formula with objects.

1 comments

>In C++, an object cannot be created by assigning a value to it, because the left hand parameter of the assignment operator is an "in out" parameter, like for any other function, so it is assumed that it already has a value of the type of that object.

While "the left hand parameter of the assignment operator is an "in out" parameter" is correct, I want to point out that

  Foo f = CreateFoo(1);
never calls the assignment operator. It either calls the move constructor, copy constructor (only if the move constructor doesn't exist) or does copy elision (meaning no copies or moves, more on that below).

>In C++ or similar languages, where the function parameters behave semantically as either "in" or "in out" parameters, a function that returns an object must have already created somehow the object.

If the object is being declared at the same time as being assigned to, and the function's return instantiates the object, e.g.

  Foo CreateFoo(int x) {
    return Foo(x);
  }
  
  Foo f = CreateFoo(1);
then as of C++17, this uses guaranteed copy elision. So there will be no copying or moving.

https://en.cppreference.com/w/cpp/language/copy_elision

Additionally, there are other situations where the compiler is allowed (but not required) to do copy elision, e.g.

  Foo CreateFoo(int x) {
    Foo result(x);
    return result;
  }
  Foo foo = CreateFoo(1);
Even if there isn't copy elision, usually move constructors (which is what will happen in the second example if the compiler decides to not do copy elision) are pretty cheap.

With Ada, if you want to call a function with an out parameter, doesn't the object for the out parameter need to be created before calling the function, and thus at least partially initialized? The article says "Non-scalar values like pointers are always initialised.'

>Therefore an assignment operator must execute the equivalent of invoking a destructor for the target of the assignment, followed by the invocation of a copy constructor.

If the assignment operator isn't used, this problem doesn't happen.

>Attempting to assign a value to memory allocated but not initialized will attempt to invoke a destructor for an invalid object value.

Can you give an example of where this would happen? This should never happen unless you're doing something strange. If you do things with standard high-level capabilities, you won't hit this problem. For example you can allocate memory without initializing it with vector.reserve() , then later initialize an object into that memory with vector.push_back() or vector.emplace_back() . If you want to go low-level (e.g. to implement vector yourself), you have to be careful to get things right, but it's possible to avoid the problem you mention by using placement new.

>All these rules about when to use constructors and when to use functions and the need to have duplicate almost identical in meaning functions for certain purposes, e.g. copy constructors and assignment operators, complicates a lot C++.

I agree it's complicated. But I don't think "in out" parameters would solve all the use cases. E.g. I don't think they can guarantee invariants like constructors and destructors can.

>There would be no need for "move" operations of any kind, which are just a trick to avoid the inefficient code generated by compilers when the semantics for some parameters is "in out" when what would have really been needed is "out".

move operations aren't just needed for output. They're also used for input. E.g.

  class Foo {
   public:
    void set_bar(std::string bar) { bar_ = std::move(bar); }
   private:
    std::string bar_;
  };

  Foo foo;
  std::string bar = CoputeBar();
  foo.set_bar(std::move(bar));
Here we can modify foo to contain a string, without copying the string. "in out" parameters wouldn't replace move semantics here in terms of avoiding copies.