Clean C ++: to "pass by value" name

To "pass by value" name

In the C++98middle, passed by value (pass-by-value) means inefficient, fearless copy, it was greeted with the majority of programmers. Accordingly the push for custom types, if shall be used for the reference pass-by-const-reference; if the parameter should be used pass-by-reference.

Deletion move semantics (move semantic)in the C++98standard, such a conclusion is understandable. However, the increasing move semantics (move semantic)under the C++11standard, by value in some special scenarios has excellent performance, it is time to "by value" name of.

one example

There are two use std::functionthe definition of "imitation function."

#include <string>
#include <functional>

using Matcher = std::function<bool(int)>;
using Action = std::function<std::string(int)>;

C ++ 98 style

Further there is a Atomfunction object. According to the existing C++98habit of use pass-by-const-referenceto pass parameters, you have to call std::functiona "copy constructor."

struct Atom {
  Atom(const Matcher& matcher, const Action& action)
    : matcher(matcher), action(action) {
  }
  
  std::string operator()(int m) const {
    return matcher(m) ? action(m) : "";
  }
  
private:
  Matcher matcher;
  Action action;
};

However, when passed to Atomthe constructor is std::functionwhen the type of "the right value", we look forward to cheaper call "mobile structure" rather than "copy-constructor." There is several solutions, one by one analysis.

Overloaded function

Use overloaded constructor can correct values ​​from the left or right implemented functions assigned value, but in order to support the heavy load of the two parameters, will have to achieve four overloaded constructor. Combinatorial explosion is a typical design flaw, in order to improve performance, this solution costs paid quite expensive.

struct Atom {
  Atom(const Matcher& matcher, const Action& action)
    : matcher(matcher), action(action) {
  }
  
  Atom(const Matcher& matcher, Action&& action)
    : matcher(matcher), action(std::move(action)) {
  }
  
  Atom(Matcher&& matcher, const Action& action)
    : matcher(std::move(matcher)), action(action) {
  }

  Atom(Matcher&& matcher, Action&& action)
    : matcher(std::move(matcher)), action(std::move(action)) {
  }
  
  std::string operator()(int m) const {
    return matcher(m) ? action(m) : "";
  }
  
private:
  Matcher matcher;
  Action action;
};

cost analysis

When the transfer value left, there is a "copy constructor"; pass the right values, there is a "moving construction", but there are unacceptable when the combinatorial explosion problem.

Mobile reference

The use of "mobile reference" can merge the four constructor, which uses a mechanism "perfect conversion" to achieve transparent transfer of left and right values. However, the use of "mobile reference" introduces a generic design, its implementation must be placed in the header file, increasing the cost of dependent compile-time, but also increases the complexity of implementation. In addition, given not 100% perfect "perfect conversion", when the client does not pass the correct type, the compiler error message is quite lengthy.

struct Atom {
  template <typename Matcher, typename Action>
  Atom(Matcher&& matcher, Action&& action)
    : matcher(std::forward<Matcher>(matcher))
    , action(std::forward<Action>(action)) {
  }
  
  std::string operator()(int m) const {
    return matcher(m) ? action(m) : "";
  }
  
private:
  Matcher matcher;
  Action action;
};

cost analysis

When the transfer value left, there is a "copy constructor"; when passing the right values, there is a "mobile structure." It avoids the problem of combinatorial explosion, but the introduction of the complexity of the template, the template and the problems of expansion; but relatively overloaded methods, the code is more concise.

Passed by value

Use pass-by-valuemode transfer Matcherand Actionnot only simple, natural support value left or right value, and the cost is within acceptable range.

struct Atom {
  Atom(Matcher matcher, Action action)
    : matcher(std::move(matcher))
    , action(std::move(action)) {
  }
  
  std::string operator()(int m) const {
    return matcher(m) ? action(m) : "";
  }
  
private:
  Matcher matcher;
  Action action;
};

cost analysis

When the transfer value left, there is a "copy constructor", and a "mobile structure"; when passing the right values, there are two times "mobile structure." Relative to the two programs, which cost more than a low-cost "mobile structure", but completely avoid the problem of combinatorial explosion, and the complexity of the template code more concise.

When passed by value

To sum up, the conditions can be summarized as follows, using the method of "pass-by-value" can be considered.

  • Produced copies;
  • Types copy;
  • Low moving costs.

For example, the above-described function object Atom, fully meet the above requirements.

  • Produced copies: by "copy constructor" or "mobile structure" to generate private matcherand actioncopy;
  • Types copy: std::functionTypes copy.
  • Low cost move: std::functionthe cost of moving construction is very low.

Application of the Lambda

Lambda may be used to simplify the Atomimplementation classes.

using Rule = std::function<std::string(int)>;

Rule atom(Matcher matcher, Action action) {
  return [matcher, action](int m) {
    return matcher(m) : action(m) : "";
  };
}

However, in "parameter capture list" in, matcher, actionit is passed by value to the closure object, in which case the inevitable call std::functionof "copy constructor." Fortunately, C++14it supports an object to be initialized to move closure object.

using Rule = std::function<std::string(int)>;

Rule atom(Matcher matcher, Action action) {
  return [matcher = std::move(matcher), action = std::move(action)](int m) {
    return matcher(m) : action(m) : "";
  };
}

Guess you like

Origin blog.csdn.net/weixin_34061482/article/details/90959641