How c++ and others are passed in function parameters, return values, and assignment operations

How classes are passed in function parameters, return values, and assignments

Reference material:

  • c++ Copy Constructor Detailed: https://www.cnblogs.com/alantu2018/p/8459250.html
  • C++: Assignment function in class: https://www.cnblogs.com/duwenxing/p/7445927.html
  • C/C++ references as return values for functions: https://blog.csdn.net/weixin_40539125/article/details/81410008
  • References to C++ pointers and pointers to references: https://blog.csdn.net/fatfish_/article/details/86768887
  • Life cycle of C++ temporary variables: https://www.cnblogs.com/catch/p/3251937.html
  • [c++11] Right-value references, mobile semantics, and perfect forwarding that I understand: https://www.jianshu.com/p/d19fc8447eaa
  • <C++ Primer Plus>

Classes have default copy functions and default assignment functions in addition to default constructors and default destructors.

When a function passes parameters and returns values by value, it first creates a temporary variable (like test T2(T1);) in a copy, which is used as a parameter and as a return value.In the C++ standard, the life cycle of a temporary variable or object ends at the end of a complete statement expression, that is, after statement T2 = fn3(T1); and so on.

As mentioned in C++ Primer Plus, references are closer to the const pointer (which needs to be initialized at creation time).So when a function passes parameters and returns values by reference, it does not create temporary variables, nor does it have the copy procedure described above.

So when a class is used as a function parameter or a return value, the default copy function (which copies all class members directly by default) is called if a temporary variable needs to be created.Normally, this is fine, but if a class data member has a pointer, the following equivalence occurs

  T3.p = new int(985);
  T4.p = T3.p;

T3 and T4 are then destructed at the end of their respective scopes, and in T3, since new is used to create memory, destructors require

  delete p;

T3 replicates completely when T4 is the same, so T4 has the same destructor, so the following equivalents occur when destructing

  delete T3.p;
  delete T4.p;

This can cause fatal hidden bug s because most compilers will not fail.(I guess the first delete frees memory that should not have been freed, and the second delete may cause data confusion)

Similarly, the default assignment function is also called when the return value of the function is assigned to another class.As with the default copy function, all class members are copied directly, and if a data member has a pointer, the above hidden bug is triggered

So how to avoid the above problems in the specific operation, let's take the following examples and explain them slowly

// test.h

// Prevent header file from being re-included
#ifndef test_h
#define test_h

#include <assert.h>
#include <iostream>

using namespace std;

class test_empty {};

class test {
 private:
  double weight = 3.0;

 public:
  double area = 9.0;
  int* p = NULL;
  test();                          //Constructor
  test(int num);                   //Constructor
  test(const test& T);             //copying functions
  test& operator=(const test& T);  //Mutator
  ~test();
};

test::test() { cout << "constructor is called." << endl; }

test::test(int num) {
  p = new int(num);
  cout << "constructor is called." << endl;
}

test::test(const test& T) {
  if (T.p == NULL) {
    weight = T.weight;
    area = T.area;
    cout << "copy constructor is called." << endl;
  } else {
    weight = T.weight;
    area = T.area;
    p = new int;
    *p = *(T.p);
    cout << "copy constructor is called." << endl;
  }
}

test& test::operator=(const test& T) {
  assert(this != &T);

  if (T.p == NULL) {
    weight = T.weight;
    area = T.area;
    cout << "assignment constructor is called." << endl;
  } else {
    weight = T.weight;
    area = T.area;
    // delete p; p = new int;
    *p = *(T.p);
    cout << "assignment constructor is called." << endl;
  }
  return *this;
}

test::~test() {
  if (p == NULL) {
    cout << "destructor is called" << endl;
  } else {
    cout << "destructor is called. before delete, ";
    cout << "p is " << p << " ,*p is " << *p << endl;
    delete p;
  }
}
#endif
// main.C

#include "test.h"

test_empty fn0(test_empty r) { return r; }
void fn1(test r) { cout << "runing fn1" << endl; }
void fn2(test& r) { cout << "runing fn2" << endl; }
test fn3(test& r) {
  cout << "runing fn3" << endl;
  return r;
}
test& fn4(test& r) {
  cout << "runing fn4" << endl;
  return r;
}
int*& fn5(test& r) {
  cout << "runing fn5" << endl;
  int* p = new int(32);
  int*& pp = p;
  return pp;
}

int main() {
  // test T1, T2;
  // cout << "/******testing without pointer in class member******/\n";
  // fn1(T1);
  // fn2(T1);
  // T2 = fn3(T1);
  // T2 = fn4(T1);
  // cout << fn4(T1).area << endl;

  test T3(985), T4(211);
  cout << "/********testing with pointer in class member*******/\n";
  fn1(T3);                       // case1: Pass parameter by value
  fn2(T3);                       // case2: Pass parameters by reference
  T4 = fn3(T3);                  // case3: Pass the return value by value and assign the value
  T4 = fn4(T3);                  // case4: Pass return value by reference and assign value
  cout << fn4(T3).area << endl;  // case5: pass return value by reference
  int*& p = fn5(T3);             // case6: Test return type

  cout << "/********************end testing********************/\n";
  return 0;
}

Running case1 alone yields the following results

constructor is called.
constructor is called.
/********testing with pointer in class member*******/
copy constructor is called.
runing fn1
destructor is called. before delete, p is 0x747ec8 ,*p is 985
/********************end testing********************/
destructor is called. before delete, p is 0x747ea8 ,*p is 211
destructor is called. before delete, p is 0x747e18 ,*p is 985

You can see that T3 creates temporary variables by copying when passing parameters by value and destructs them at the end of the function line.To prevent the pointer p from being freed repeatedly, we customize the copy function of the test class so that the p of the temporary variable points to the new memory space; of course, it is also possible, like case2, to reference without creating a temporary variable without having two pointers pointing to the same memory space, which is also a very recommended daily operation.

In test (const test &T), const is used to call both const and non-const quantities and to prevent user modification. Test &is used to prevent the copy function from building temporary variables on its own and then recursively indefinitely (when I test, the direct compiler directly errors, and test(const test T) is not allowed).

Running case2 separately results in the following

constructor is called.
constructor is called.
/********testing with pointer in class member*******/
runing fn2
/********************end testing********************/
destructor is called. before delete, p is 0x1d7ea8 ,*p is 211
destructor is called. before delete, p is 0x1d7e18 ,*p is 985

Running case3 alone yields the following results

constructor is called.
constructor is called.
/********testing with pointer in class member*******/
runing fn3
copy constructor is called.
assignment constructor is called.
destructor is called. before delete, p is 0x37ec8 ,*p is 985
/********************end testing********************/
destructor is called. before delete, p is 0x37ea8 ,*p is 985
destructor is called. before delete, p is 0x37e18 ,*p is 985

The scope of the temporary variable mentioned earlier is validated here: first, at the end of function fn3, a temporary variable is created as a return value through a copy function, then assigned to T4 through an assignment function, and finally, the temporary variable is released at the end of the complete statement (that is, at) in function fn3.

At the same time, the idea of the custom assignment function (overload=) is the same as that of the custom copy function, except that the pointer p of the variable T4 has already allocated memory and only needs to be redirected to the same value; and the return value is required.

I've also tried changing the definition of an assignment function, and when it's defined as test operator= (const test & T), the results are as follows

constructor is called.
constructor is called.
/********testing with pointer in class member*******/
runing fn3
copy constructor is called.
assignment constructor is called.
copy constructor is called.
destructor is called. before delete, p is 0x9f7ee8 ,*p is 985
destructor is called. before delete, p is 0x9f7ec8 ,*p is 985
/********************end testing********************/
destructor is called. before delete, p is 0x9f7ea8 ,*p is 985
destructor is called. before delete, p is 0x9f7e18 ,*p is 985

That is, a temporary variable is created when you return the value yourself.An interesting phenomenon occurred when it was defined as test & operator=(const test T), and the results were consistent with case3 or keep the change running T4 = T3.Here, the compiler turns on return value optimization by default, assuming that the temporary variable of the temporary variable is itself and does not need to call copy function generation.Because the gdb discovers that the temporary variable created when the function fn3 returns a value, the next step becomes the parameter of the assignment function directly, that is, the return value of fn3 and the parameter of the assignment function are the same temporary variable; at the same time, if return value optimization is turned off at compile time (-fno-elide-constructors, see https://www.jianshu.com/p/d19fc8447eaa), the results are as follows

constructor is called.
constructor is called.
/********testing with pointer in class member*******/
runing fn3
copy constructor is called.
copy constructor is called.
assignment constructor is called.
destructor is called. before delete, p is 0x8673e8 ,*p is 985
destructor is called. before delete, p is 0x8673c8 ,*p is 985
/********************end testing********************/
destructor is called. before delete, p is 0x8673a8 ,*p is 985
destructor is called. before delete, p is 0x867f70 ,*p is 985

Running case4 alone yields the following results

constructor is called.
constructor is called.
/********testing with pointer in class member*******/
runing fn4
assignment constructor is called.
/********************end testing********************/
destructor is called. before delete, p is 0x6d7ea8 ,*p is 985
destructor is called. before delete, p is 0x6d7e18 ,*p is 985

Running case5 alone yields the following results

constructor is called.
constructor is called.
/********testing with pointer in class member*******/
runing fn4
9
/********************end testing********************/
destructor is called. before delete, p is 0x1c7ea8 ,*p is 211
destructor is called. before delete, p is 0x1c7e18 ,*p is 985

Summary:

  • When class members have pointers, prevent different pointers from pointing to the same address when functions pass parameters and return values, and assign values.There are two solutions: one is to customize the copy function and assignment function (that is, overload=); the other is to use references whenever possible, and you can also define an empty private copy function and assignment function, so that if you forget to use references, the compiler will give you an error alert.

  • How does the compiler determine whether to use constructors, copy functions, or assignments?Here are a few examples:

    • Constructor: used when initialization involves only a single variable, such as: test T3(985), T5 = test(133); test(133);
    • Copy function: Initialization involves only two variables, as well as function parameters or return values, such as: test T5 = T3, T6(T3); etc.
    • Assignment function, used when uninitialization involves only two variables, such as T3 = test(133); and T5 = T3;
    • Initialize the temporary variable call the constructor, copy the variable to the temporary variable call the copy function.For a discussion of temporary variables, see https://www.cnblogs.com/catch/p/3251937.html, case3 contradicts its conclusion that temporary variables are created when return values are not assigned to other variables.After reading https://www.jianshu.com/p/d19fc8447eaa, T4 = fn3(T3); in c's sense, fn3(T3) should be the right value, while local variables are the left value and temporary variables are the right value, so temporary variables should be generated
  • More than one copy function may exist in a class if the first parameter of a constructor is one of the following:

    • X&
    • const X&
    • volatile X&
    • const volatile X&

    If no other parameter or other parameter has a default value, then this function is a copy function.

  • Constructors can be overloaded, destructors cannot.

  • A return value is also required when customizing an assignment function, which is defined as test & operator= (const test & T).If it is defined as viod operator= (const test & T), overloaded = cannot achieve continuous assignments, such as T3 = T2 = T1; if it is defined as test operator= (const test & T), the copy function is called on return.

  • When designing functions, be aware that you cannot return references to local variables.For example, if the return value in fn3 is a reference, the return value cannot be a local variable, otherwise the compiler will error.References also explain that if T4 is also referenced at this time, it will cause the T4 point to be empty at the end of the function line.But the compiler and errors will occur when I test, so I don't describe them here.

  • When designing a function, be aware that it is not possible to return memory (or its references) allocated by new within the function.When I tested case6, I found that the compiler did not make a mistake, mainly considering that no delete could cause a memory leak.

  • When designing a function, you can use the reference returned by the function as the left value in the assignment expression; the reference to the base class can be used to bind instances of its derived class, such as Father &ptr=son; and //Initialize the use of the base class object with the object of the derived class, which I'm still learning.

Two original articles have been published. Approved 0. Visits 8
Private letter follow

Tags: Mobile

Posted on Mon, 10 Feb 2020 23:47:20 -0500 by rage2021