Object

C++ programs create, destroy, refer to, access, and manipulate objects.

An object, in C++, is a region of storage that has.

Functions, references, classes and other types, namespaces, enumerators, and templates are not objects.

Objects are created by definitions, new-expressions, and in several other situations where temporary objects are required: binding a reference to a prvalue, returning a prvalue from a function, a conversion that creates a prvalue, throwing an exception, entering an exception handler, and in some initialization contexts.

Object representation and value representation

For an object of type T, object representation is the sequence of sizeof(T) objects of type unsigned char beginning at the same address as the T object.

The value representation of an object is the set of bits that hold the value of its type T.

For TriviallyCopyable types, value representation is a part of the object representation, which means that copying the bytes occupied by the object in the storage is sufficient to produce another object with the same value (except if the value is a trap representation of its type and loading it into the CPU raises a hardware exception, such as SNaN ("signalling not-a-number") floating-point values or NaT ("not-a-thing") integers).

The reverse is not necessarily true: two objects of TriviallyCopyable type with different object representations may represent the same value. For example, multiple floating-point bit patterns represent the same special value NaN. More commonly, some bits of the object representation may not participate in the value representation at all; such bits may be padding introduced to satisfy alignment requirements, bit field sizes, etc.

#include <cassert>
struct S {
    char c;  // 1 byte value
             // 3 bytes padding
    float f; // 4 bytes value
    bool operator==(const S& arg) const { // value-based equality
        return c == arg.c && f == arg.f;
    }
};
assert(sizeof(S) == 8);
S s1 = {'a', 3.14};
S s2 = s1;
reinterpret_cast<char*>(&s1)[2] = 'b'; // change 2nd byte
assert(s1 == s2); // value did not change

For the objects of type char, signed char, and unsigned char, every bit of the object representation is required to participate in the value representation and each possible bit pattern represents a distinct value (no padding, trap bits, or multiple representations allowed).

Subobjects

An object can contain other objects, which are called subobjects. These include.

  • member objects
  • base class subobjects
  • array elements

An object that is not a subobject of another object is called complete object.

Complete objects, member objects, and array elements are also known as most derived objects, to distinguish them from base class subobjects. The size of a most derived object that is not a bit field is required to be non-zero (the size of a base class subobject may be zero: see empty base optimization).

Any two objects (that are not bit fields) are guaranteed to have different addresses unless one of them is a subobject of another, or if they are subobjects of different type within the same complete object, and one of them is a zero-size base.

static const char c1 = ’x’;
static const char c2 = ’x’;
assert(&c1 != &c2); // same values, different addresses

Polymorphic objects

Objects of class type that declare or inherit at least one virtual function are polymorphic objects. Within each polymorphic object, the implementation stores additional information (in every existing implementation, it is one pointer unless optimized out), which is used by virtual function calls and by the RTTI features (dynamic_cast and typeid) to determine, at run time, the type with which the object was created, regardless of the expression it is used in.

For non-polymorphic objects, the interpretation of the value is determined from the expression in which the object is used, and is decided at compile time.

#include <iostream>
#include <typeinfo>
struct Base1 {
    // polymorphic type: declares a virtual member
    virtual ~Base1() {}
};
struct Derived1 : Base1 {
     // polymorphic type: inherits a virtual member
};
 
struct Base2 {
     // non-polymorphic type
};
struct Derived2 : Base2 {
     // non-polymorphic type
};
 
int main()
{
    Derived1 obj1; // object1 created with type Derived1
    Derived2 obj2; // object2 created with type Derived2
 
    Base1& b1 = obj1; // b1 refers to the object obj1
    Base2& b2 = obj2; // b2 refers to the object obj2
 
    std::cout << "Expression type of b1: " << typeid(decltype(b1)).name() << ' '
              << "Expression type of b2: " << typeid(decltype(b2)).name() << '\n'
              << "Object type of b1: " << typeid(b1).name() << ' '
              << "Object type of b2: " << typeid(b2).name() << '\n'
              << "size of b1: " << sizeof b1 << ' '
              << "size of b2: " << sizeof b2 << '\n';
}

Output:

Expression type of b1: Base1 Expression type of b2: Base2
Object type of b1: Derived1 Object type of b2: Base2
size of b1: 8 size of b2: 1

Strict aliasing

Accessing an object using an expression of a type other than the type with which it was created is undefined behavior in many cases, see reinterpret_cast for the list of exceptions and examples.

Alignment

Every object type has the property called alignment requirement, which is an integer value (of type std::size_t, always a power of 2) representing the number of bytes between successive addresses at which objects of this type can be allocated. The alignment requirement of a type can be queried with alignof or std::alignment_of. The pointer alignment function std::align can be used to obtain a suitably-aligned pointer within some buffer, and std::aligned_storage can be used to obtain suitably-aligned storage.

Each object type imposes its alignment requirement on every object of that type; stricter alignment (with larger alignment requirement) can be requested using alignas.

In order to satisfy alignment requirements of all non-static members of a class, padding may be inserted after some of its members.

#include <iostream>
 
// objects of type S can be allocated at any address
// because both S.a and S.b can be allocated at any address
struct S {
  char a; // size: 1, alignment: 1
  char b; // size: 1, alignment: 1
}; // size: 2, alignment: 1
 
// objects of type X must be allocated at 4-byte boundaries
// because X.n must be allocated at 4-byte boundaries
// because int's alignment requirement is (usually) 4
struct X {
  int n;  // size: 4, alignment: 4
  char c; // size: 1, alignment: 1
  // three bytes padding
}; // size: 8, alignment: 4 
 
int main()
{
    std::cout << "sizeof(S) = " << sizeof(S)
              << " alignof(S) = " << alignof(S) << '\n';
    std::cout << "sizeof(X) = " << sizeof(X)
              << " alignof(X) = " << alignof(X) << '\n';
}

Output:

sizeof(S) = 2 alignof(S) = 1
sizeof(X) = 8 alignof(X) = 4

The weakest alignment (the smallest alignment requirement) is the alignment of char (typically 1); the largest fundamental alignment of any type is the alignment of std::max_align_t. If a type's alignment is made stricter (larger) than std::max_align_t using alignas, it is known as a type with extended alignment requirement. A type whose alignment is extended or a class type whose non-static data member has extended alignment is an over-aligned type. It is implementation-defined if new-expression, std::allocator::allocate, and std::get_temporary_buffer support over-aligned types. Allocators instantiated with over-aligned types are allowed to fail to instantiate at compile time, to throw std::bad_alloc at runtime, to silently ignore unsupported alignment requirement, or to handle them correctly.

See also

C documentation for object
doc_CPP
2016-10-11 09:59:25
Comments
Leave a Comment

Please login to continue.