Is there any advantage to implementing functions as free functions rather than members in C ++?

I am interested in technical logistics. Are there any advantages, such as saving memory, etc., for implementing certain functions related to the class?

In particular, the implementation of operator overloading as free functions (provided that you do not need access to any private members, and even then you can force them to use a friend who is not a member)?

Is there a separate memory address for each class function every time an object is created?

+5
source share
6 answers

This answer may help you: Operator overload: member function or non-member function? . In general, free functions are mandatory if you need to implement operators on classes that you donโ€™t have access to the source of the code (think of stream s), or if the left operand is not of the class type (for example, int ). If you control the class code, you are free to use the members of the function.

There is no question for your last question, the members of the function are uniquely defined, and an internal table of the object is used to indicate them. The members of a function can be considered as free functions with a hidden parameter, which is a pointer to an object, i.e. of(a) more or less coincides with f(&o,a) with the prototype approximately like f(C *this,A a); .

+3
source

There are various articles about circumstances where the implementation of functionality using functions other than members is preferable to the elements of a function.

Examples include

Scott Myers (author of Effective C ++, Effective STL, and others) on how non-members improve encapsulation: http://www.drdobbs.com/cpp/how-non-member-functions-improve-encapsu / 184401197 p>

Grass Sutter in the series of his Guru of the week # 84 "Unstrung Monoliths". In essence, it protects when it is possible to implement functionality as a member or as a non-member that is not a friend, prefers a variant that is not a member. http://www.gotw.ca/gotw/084.htm

+5
source

Non- static member functions have an implicit this parameter. If your function does not use any non- static elements, it should be either a free function or a static member function, depending on what namespace you want it to be . This will avoid confusion for people reading (who will scratch their heads looking for a non- static reason), and will be a slight improvement in code size, which probably cannot be measured in performance.

To be clear: in asm, there is zero difference between the static member function and the non-member function . The choice between static-member and global or static (file area) is purely a namespace quality and design issue, not a performance issue. (In Unix-shared libraries (position-independent code), calls to global functions have a level of indirection via PLT, so prefer static to scope functions of the file. This is another value for the keyword static vs. global static -member, which are globally visible and therefore subject to symbolic movement .)


One possible exception to this rule is that wrapper functions that pass most of their arguments unchanged to another function can have their arguments in the same order as the function they call, so they donโ€™t need to move them between registers, for example, if a member function does something simple for a class member, and then calls a static member function with the same list of arguments, it actually does not have an implicit this pointer, so all arguments must be moved in the same register.


Most ABIs use the args-in-registers calling convention. 32-bit x86 (different from some Windows conventions) is the main exception that I know of, where all arguments are always passed to the stack. 64-bit x86 passes the first 6 integer arguments to registers, and the first 8 arguments to FP in hmm registers ( SysV ). Or the first 4 arguments of args of any type in registers (Windows).

Passing an object pointer usually takes an extra team or two at each call site. If the implicit first arg removes any other arguments from the limited set of arg-pass regs, then it must be pushed onto the stack. This adds several latency cycles to the critical path, including this argument for round trip, as well as additional instructions for the called party and the calling party. (See the wiki for links to other details about this, for this platform).

An attachment, of course, eliminates this. static functions can also be optimized by modern compilers, since the compiler knows that all calls come from code that he can see, so he can make them non-standard. IDK, if any compiler fails unused arguments during optimization between procedures. Link time and / or optimization of the entire program can also reduce or eliminate overhead from unused arguments.

The size of the code always matters, at least a little, since smaller files load from disk faster and skip less in the I-cache. I do not expect any measurable difference in speed unless you specifically design an experiment that is sensitive to it.

+2
source

One strictly technical difference, which is also true for static vs non static member functions, can affect performance in extreme scenarios:

For a member function, the this pointer will be passed as an "invisible" parameter to the function. Usually, depending on the types of parameters, a fixed number of parameter values โ€‹โ€‹can be transmitted through the registers, and not through the stack (registers are read and written faster).

If the function already accepts this number of parameters explicitly, then the inclusion of a non- static member function can cause the parameters to be passed through the stack and not through the registers, and if this happens, optimization is prohibited, which may or may not happen, the function call will be slower.

However, even if it is slower - in this case, in the vast majority of use cases that you can dream of, the slower one is insignificant (but real).

+1
source

Depending on the object, class functions may not be the right solution. Class functions depend on the assumption that there is an asymmetry between the arguments of a critical non-classical function, where it is the pure individualized main subject of the function (id est, implicitly passing this, which practically corresponds to passing the object by reference). On the other hand, many times such an asymmetry may not exist. In these cases, free functions are the best solution. As for the speed of execution, there is no difference, because the class method is just a function where this pointer is the first argument. Thus, this is absolutely equivalent to the corrupt non class function, where the first element is a pointer to an object.

0
source

The most important thing to consider when designing classes is "what is an invariant"? Classes are designed to protect invariants. Therefore, classes should be as feasible as possible to ensure that the invariant is properly protected. If you have so many member / friend features, there is more code to view.

From this point of view, if a class has members that do not need protection (for example, a logical value that its corresponding get / set functions can freely change by the user), it is better to put these attributes as public and delete the get / set functions (more or less , these are the words of Bjarne Stroustrup).

So which functions should be declared inside the class and which ones? Internal functions should be this minimum necessary set of functions for protecting the invariant, and external functions should be any function that can be implemented using others.

The thing with operator overloading is another story, because the criteria to put some operators inside and some other external ones is due to syntax problems associated with implicit conversions, etc.:

 class A { private: int i_i; public: A(int i) noexcept : i_i(i) {} int val() const noexcept { return i_i; } A operator+(A const& other) const noexcept { return A(i_i + other.i_i); } }; A a(5); cout << (4 + a).val() << endl; 

In this case, since the operator is defined inside the class, the compiler does not find this operator because the first argument is an integer (when the operator is called, search for the compiler of free functions and functions declared inside the class of the first argument).

When advertised outside:

 class A { private: int i_i; public: A(int i) noexcept : i_i(i) {} int val() const noexcept { return i_i; } }; inline A operator+(A const& first, A const& other) const noexcept; { return A(first.val() + other.val()); } A a(5); cout << (4 + a).i_i << endl; 

In this case, the compiler finds this statement and tries to perform an implicit conversion of the first parameter from int to A using its own constructor A.

In this case, the operator can also be implemented using other functions, so it does not need to be friend , and you can be sure that the invariant is not compromised with this additional function. Thus, in this particular example, moving the operator outside is good for two reasons.

0
source

Source: https://habr.com/ru/post/1247773/


All Articles