c++ - 在自己的构造中使用对象有什么问题吗?

标签 c++ c++11

我正在存储一个需要对对象进行操作的操作,但我不想使用继承。所以我使用的技术是有一个非成员函数,它接受一个指向对象的指针,然后将它存储在对象中,如下所示:

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;
}

我的问题是执行 command com(std::bind(test_action, &com)); 是否安全?还是未定义的行为?

最佳答案

首先:什么是对象?

[intro.object]\1

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

在对象的生命周期开始之前分配存储空间:

[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.

因此,指针指向分配的空间,使用它没有害处。你只是要求一个堆栈地址,任何编译器都应该能够正确地计算出来。在这个特定的实例中,不需要对象本身的初始化操作。

这是有道理的,因为在经典的 AST-fashion 编译器中,如果您在一个简单的玩具代码中查看声明符的标准层次结构,就像

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

int funct(command*) {
    return 2;
}

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

线

command com(funct(&com));

解释如下:

[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

最后,对于您的代码,这就是 gcc 编译这一行的方式 (-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&)

也就是说:只是一堆来自基指针的堆栈地址,它们在调用构造函数之前被传递给绑定(bind)函数。

如果您在构造对象之前实际尝试使用该对象,情况会有所不同(使用虚函数表可能会变得棘手)。

旁注:如果您复制对象或按值传递对象并且超出范围(并且仍然保留堆栈位置的地址),这保证是安全的。另外:如果编译器决定将其存储(无论出于何种架构/原因)作为与基本框架的偏移量,那么您可能会进入未定义的行为领域。

关于c++ - 在自己的构造中使用对象有什么问题吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26074373/

相关文章:

c++ - unsigned int 导致无限循环

c++ - 调用模板显式实例化时如何开启自动匹配?

C++11 与现有库/框架的兼容性

c++ - unique_ptr 不调用析构函数来释放指针

c++ - C标准: Character set and string encoding specification

C++:从满足特定要求的二维数组的元素中创建一个数组

java - 解决词链的最佳方法

c++ - 如何在 Visual C++ 2013 上递归删除二叉树?

c++ - static const double/float 在一个类中,好还是不好,请给我一个当前的答案

c++ - 为什么我的 RLE 代码显示 std out of range for c++?