Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there anything wrong with using an object in its own construction?

Tags:

c++

c++11

I'm storing an action that needs to operate on an object but I don't want to use inheritance. So the technique I'm using is to have a non-member function that accepts a pointer to an object and then store it in the object, like so:

struct command
{
    command()
    {
    }

    command(const std::function<void()>& action)
        : action(action)
    {
    }

    int n;
    std::function<void()> action;
};

void test_action(command* this_ptr)
{
    this_ptr->n = 5;
}


int main()
{
    command com(std::bind(test_action, &com));
    com.action();
    std::cout << com.n;
}

My question is it safe to do command com(std::bind(test_action, &com));? Or is it undefined behavior?

like image 376
user4085715 Avatar asked Sep 27 '14 11:09

user4085715


1 Answers

First off: what is an object?

[intro.object]\1

[...] An object is a region of storage [...]

The storage is allocated before the lifetime of an object starts:

[basic.life]

Before the lifetime of an object has started but after the storage which the object will occupy has been allocated [..] any pointer that refers to the storage location where the object will be or was located may be used but only in limited ways. For an object under construction or destruction, see 12.7 [construction and destruction]. Otherwise, such a pointer refers to allocated storage (3.7.4.2), and using the pointer as if the pointer were of type void*, is well-defined.

Thus the pointer refers to allocated space and there's no harm in using it. You're just asking for a stack address and any compiler should be able to figure it out correctly. No initialization operation by the object itself is required in this specific instance.

This makes sense since in a classic AST-fashion compiler if you to take a look at the standard hierarchy for declarators, in a simple toy-code like

class command {
public:
  command(int) {
  }
};

int funct(command*) {
    return 2;
}

int main() {
    command com(funct(&com));
}

the line

command com(funct(&com));

is interpreted as follows:

[dcl.decl]

simple-declaration:
    attribute-specifier-seqopt decl-specifier-seqopt init-declarator-listopt;
       ...
         initializer:
            brace-or-equal-initializer
                ( expression-list ) // The declaration statement is already specified

And finally for your code this is how gcc compiles this line (-O0)

command com(std::bind(test_action, &com));

->

movq    %rax, -104(%rbp)
leaq    -104(%rbp), %rdx
leaq    -96(%rbp), %rcx
movl    test_action(command*), %esi
movq    %rcx, %rdi
movq    %rax, -136(%rbp)        # 8-byte Spill
movq    %rcx, -144(%rbp)        # 8-byte Spill
callq   _ZSt4bindIRFvP7commandEJS1_EENSt12_Bind_helperIT_JDpT0_EE4typeEOS5_DpOS6_
leaq    -80(%rbp), %rax
movq    %rax, %rdi
movq    -144(%rbp), %rsi        # 8-byte Reload
movq    %rax, -152(%rbp)        # 8-byte Spill
callq   _ZNSt8functionIFvvEEC1ISt5_BindIFPFvP7commandES5_EEEET_NSt9enable_ifIXntsr11is_integralISA_EE5valueENS1_8_UselessEE4typeE
movq    -136(%rbp), %rdi        # 8-byte Reload
movq    -152(%rbp), %rsi        # 8-byte Reload
callq   command::command(std::function<void ()> const&)

which is: just a bunch of stack addresses from the base pointer which get passed to the binding function before invoking the constructor.

Things would be different if you actually tried to use the object before its construction (things might get tricky with virtual function tables).

Sidenote: this is NOT guaranteed to be safe if you're copying around or passing by value the object and going out-of-scope (and still keeping an address to the stack location). Also: if the compiler decides to store it (for whatever architecture/reason) as an offset from the base frame, you're probably off to undefined behaviorland.

like image 60
Marco A. Avatar answered Sep 16 '22 15:09

Marco A.