An example of supporting pass statements by value is not good practice even for small custom types

I read Effective C ++ by Scott meyers, and the author compared the pass by value and passed by reference. For a user-defined type, it is recommended to use a pass by reference and for the built-in pass type by value. I am looking for an example to explain the following paragraph, which states that skipping by value can be expensive even for a small user object.

Built-in types are small, so some people conclude that all small types are good candidates for passing by value, even if they are user-defined. This is a shaky reasoning. Just because the object is small does not mean that calling its copy constructor is inexpensive. Many objects — most STL containers among them — contain a little more than a pointer, but copying such objects entails copying everything that they point to. Which can be very expensive.

+4
source share
4 answers

It depends on whether your copy is a deep or shallow copy (or a class similar to a class / pointer). For example, A is a class with one pointer to another object:

struct B;
struct A
{
   B* pB;
   ~A{delete pB;}
}a1,a2;

A , a1=a2, , , pB a1, a2 . , dtor ~A() , undefined.

, :

   struct A
   { 
    B* pB;
    const A& operator=(const A&rhs)
    {
      if(this!=&rhs)
      {
        delete pB;
        pB=new pB;
        *pB=*rhs.pB;
      }
      return *this;
    }
    //the copy/move constructor/assignment should also be redefined
     ~A{delete pB;}
  }a1,a2

B, .

, -, , .

undefined, shared_ptr . , @Arne Vogel, shared_ptr , , .

+2

"" CPU.

, :

#include <iostream>

class simple {
    public:
        simple() { std::cout << "constructor" << std::endl; }
        simple(const simple& copy) { std::cout << "copied" << std::endl; }
        ~simple() { std::cout << "destructor" << std::endl; }
        void addr() const { std::cout << &(*this) << std::endl; }
};

void simple_ref(const simple& ref) { ref.addr(); }
void simple_val(simple val) { val.addr(); }

int main(int argc, char* argv[])
{
    simple val;      // output: 'constructor'
    simple_ref(val); // output: address of val
    simple_val(val); // output: 'copied', address of copy made, 'destructor' (the destructor of the copy made)
    return 0;
    // output: 'destructor' (destructor of 'val')
}

-, sizeof(simple) 1, , , , , - , .

, - , , , .

, .

+1

, .

, , , , . , , .

0

( blogpost vs ref, Thiago Macieira, https://www.macieira.org/blog/2012/02/the-value-of-passing-by-value/)

ABI , , . , Im ++-: , ? - qreal-?

, QLatin1String, , . ?

  • 32- 64-
  • ( )
  • , Qt

Ill x86-64, ARMv7, MIPS hard-float (o32) IA-64 ABI, , . 4 , . MIPS, 4 , . . ABI.

, , , :

struct Pointers2
{
    void *p1, *p2;
};
struct Pointers4
{
    void *p1, *p2, *p3, *p4;
};
struct Integers2 // like QSize and QPoint
{
    int i1, i2;
};
struct Integers4 // like QRect
{
    int i1, i2, i3, i4;
};
template <typename F> struct Floats2 // like QSizeF, QPointF, QVector2D
{
    F f1, f2;
};
template <typename F> struct Floats3 // like QVector3D
{
    F f1, f2, f3;
};
template <typename F> struct Floats4 // like QRectF, QVector4D
{
    F f1, f2, f3, f4;
};
template <typename F> struct Matrix4x4 // like QGenericMatrix<4, 4>
{
    F m[4][4];
};
struct QChar
{
    unsigned short ucs;
};
struct QLatin1String
{
    const char *str;
    int len;
};
template <typename F> struct QMatrix
{
    F _m11, _m12, _m21, _m22, _dx, _dy;
};
template <typename F> struct QMatrix4x4 // like QMatrix4x4
{
    F m[4][4];
    int f;
};

:

template <typename T> void externalFunction(T);
template <typename T> void passOne()
{
    externalFunction(T());
}
template <typename T> T externalReturningFunction();
template <typename T> void returnOne()
{
    externalReturningFunction<T>();
}
// C++11 explicit template instantiation
template void passOne<Pointers2>();
template void passOne<Pointers4>();
template void passOne<Integers2>();
template void passOne<Integers4>();
template void passOne<Floats2<float> >();
template void passOne<Floats2<double> >();
template void passOne<Floats3<float> >();
template void passOne<Floats3<double> >();
template void passOne<Floats4<float> >();
template void passOne<Floats4<double> >();
template void passOne<Matrix4x4<float> >();
template void passOne<Matrix4x4<double> >();
template void passOne<QChar>();
template void passOne<QLatin1String>();
template void passOne<QMatrix<float> >();
template void passOne<QMatrix<double> >();
template void passOne<QMatrix4x4<float> >();
template void passOne<QMatrix4x4<double> >();
template void returnOne<Pointers2>();
template void returnOne<Pointers4>();
template void returnOne<Integers2>();
template void returnOne<Integers4>();
template void returnOne<Floats2<float> >();
template void returnOne<Floats2<double> >();
template void returnOne<Floats3<float> >();
template void returnOne<Floats3<double> >();
template void returnOne<Floats4<float> >();
template void returnOne<Floats4<double> >();
template void returnOne<Matrix4x4<float> >();
template void returnOne<Matrix4x4<double> >();
template void returnOne<QChar>();
template void returnOne<QLatin1String>();
template void returnOne<QMatrix<float> >();
template void returnOne<QMatrix<double> >();
template void returnOne<QMatrix4x4<float> >();
template void returnOne<QMatrix4x4<double> >();

, , : ? :

void passFloat()
{
    void externalFloat(float, float, float, float);
    externalFloat(1.0f, 2.0f, 3.0f, 4.0f);
}
void passDouble()
{
    void externalDouble(double, double, double, double);
    externalDouble(1.0f, 2.0f, 3.0f, 4.0f);
}
float returnFloat()
{
    return 1.0f;
}
double returnDouble()
{
    return 1.0;
}
Analysis of the output
x86-64

, , 32- x86 . , . , :

whether the structures are stored in the stack in the place of the argument, or whether they’re stored elsewhere and it’s passed by pointer
whether single-precision floating-point is promoted to double-precision

, Im , , ILP32 ABI x86-64, GCC 4.7 -mx32, ABI, ( 32-).

, . ,

Pointers2 is passed in registers;
Pointers4 is passed in memory;
Integers2 is passed in a single register (two 32-bit values per 64-bit register);
Integers4 is passed in two registers only (two 32-bit values per 64-bit register);
Floats2<float> is passed packed into a single SSE register, no promotion to double
Floats3<float> is passed packed into two SSE registers, no promotion to double;
Floats4<float> is passed packed into two SSE registers, no promotion to double;
Floats2<double> is passed in two SSE registers, one value per register
Floats3<double> and Floats4<double> are passed in memory;
Matrix4x4 and QMatrix4x4 are passed in memory regardless of the underlying type;
QChar is passed in a register;
QLatin1String is passed in registers.
The floating point parameters are passed one per register, without float promotion to double.

, : , ; , . , ABI:

Single-precision floating-point types are not promoted to double;
Single-precision floating-point types in a structure are packed into SSE registers if they are still available
Structures bigger than 16 bytes are passed in memory, with an exception for __m256, the type corresponding to one AVX 256-bit register.

IA-64

:

Both Pointers structures are passed in registers, one pointer per register;
Both Integers structures are passed in registers, packed like x86-64 (two ints per register);
All of the Floats structures are passed in registers, one value per register (unpacked);
QMatrix4x4<float> is passed entirely in registers: half of it (the first 8 floats) are in floating-point registers, one value per register (unpacked); the other half is passed in integer registers out4 to out7 as the memory representations (packed);
QMatrix4x4<double> is passed partly in registers: half of it (the first 8 doubles) are in floating-point registers, one value per register (unpacked); the other half is passed in memory;
QChar and QLatin1String are passed in registers;
Both QMatrix are passed entirely in registers, one value per register (unpacked);
QMatrix4x4 is passed like Matrix4x4, except that the integer is always in memory (the structure is larger than 8*8 bytes);
Individual floating-point parameters are passed one per register; type promotion happens internally in the register.

:

The floating-point structures with up to 8 floating-point members are returned in registers;
The integer structures of up to 32 bytes are returned in registers;
All the rest is returned in memory supplied by the caller.

:

Type promotion happens in hardware, as IA-64 does not have specific registers for single or double precision (is FP registers hold only extended precision data);
Homogeneous structures of floating-point types are passed in registers, up to 8 values; the rest goes to the integer registers if there are some still available or in memory;
All other structures are passed in the integer registers, up to 64 bytes;
Integer registers are allocated for passing any and all types, even if they aren't used (the ABI says they should be used if in the case of C without prototypes).

ARM

ARMv7, VFP. , , , , ARM. " ". , ARMv7: ARMv8 64-bit (AArch64) , .

:

Pointers2, Pointers4, Integers2, and Integers4 are passed in registers (note that the Pointers and Integers structures are the same in 32-bit mode);
All of the Float types are passed in registers, one value per register, without promotion of floats to doubles; the values are also stored in memory but I can't tell if this is required or just GCC being dumb;
All types of Matrix4x4, QMatrix and QMatrix4x4 are passed in both memory and registers, which contains the first 16 bytes;
QChar and QLatin1String are passed in registers;
are passed in memory regardless of the underlying type.
The floating point parameters are passed one per register, without float promotion to double.

:

All of the Float types are returned in registers and GCC then stores them all to memory even if they are never used afterwards;
QChar is returned in a register;
Everything else is returned in memory.

, , 32- AAPCS 64-: , , , . 32- AAPCS 4 .

:

Single-precision floating-point types are not promoted to double;
Homogeneous structures (that is, structures containing one single type) of a floating-point type are passed in floating-point registers if the structure has 4 members or fewer;

MIPS

32- MIPS ( GCC-default o32 ABI), 64- MIPS ( -mabi = o64 -mlong64). , .

:

Both types of Integers and Pointers structures are passed in registers; on 64-bit, two 32-bit integers are packed into a single 64-bit register like x86-64;
Float2<float>, Float3<float>, and Float4<float> are passed in integer registers, not on the floating-point registers; on 64-bit, two floats are packed into a single 64-bit register;
Float2<double> is passed in integer registers; on 32-bit, two 32-bit registers are required to store each double;
On 32-bit, the first two doubles of Float3<double> and Float3<double> are passed in integer registers, the rest are passed in memory;
On 64-bit, Float3<double> and Float3<double> are passed entirely in integer registers;
Matrix4x4, QMatrix, and QMatrix4x4 are passed in integer registers (the portion that fits) and in memory (the rest);
QChar is passed in a register (on MIPS big-endian, it passed on bits 16-31);
QLatin1String is passed on two registers;
The floating point parameters are passed one per register, without float promotion to double.

MIPS : , QChar.

:

No float is promoted to double;
No structure is ever passed in floating-point registers;
No structure is ever returned in registers.

, . , , . , , , (...), , . - IA-64, , , x87, .

, ( ), : , . , , , , - , MIPS . , , , , , .

: , , , , .

, MIPS, . , , ABI , . ABI, . , .

- x86-64: - 16 , SSE-. , 16 . , , .

(ARM IA-64) ( ). IA-64 , , , ARM.

Structures of up to 16 bytes containing integers and pointers should be passed by value;
Homogeneous structures of up to 16 bytes containing floating-point should be passed by value (2 doubles or 4 floats);
Mixed-type structures should be avoided; if they exist, passing by value is still a good idea;

, - -. C- (POD ++) .

, . , , (GCC 4.6, Clang 3.0, ICC 12.1) - . , . , , .

. , x86-64, -, . , , .

0
source

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


All Articles