有没有办法在未命名的类中声明构造函数或析构函数?考虑以下
void f()
{
struct {
// some implementation
} inst1, inst2;
// f implementation - usage of instances
}
后续问题:实例当然是作为任何基于堆栈的对象构造(和销毁)的。什么叫?它是编译器自动分配的错误名称吗?
最佳答案
最简单的解决方案是将命名的结构实例作为成员放入未命名的实例中,并将所有功能放入命名实例中。这大概是唯一兼容C++98的方式了。
#include <iostream>
#include <cmath>
int main() {
struct {
struct S {
double a;
int b;
S() : a(sqrt(4)), b(42) { std::cout << "constructed" << std::endl; }
~S() { std::cout << "destructed" << std::endl; }
} s;
} instance1, instance2;
std::cout << "body" << std::endl;
}
接下来的一切都需要 C++11 值初始化支持。
为了避免嵌套,构造的解决方案很简单。您应该对所有成员使用 C++11 值初始化。您可以使用 lambda 调用的结果来初始化它们,因此您可以在初始化期间真正执行任意复杂的代码。
#include <iostream>
#include <cmath>
int main() {
struct {
double a { sqrt(4) };
int b { []{
std::cout << "constructed" << std::endl;
return 42; }()
};
} instance1, instance2;
}
您当然可以将所有“构造函数”代码推到单独的成员中:
int b { [this]{ constructor(); return 42; }() };
void constructor() {
std::cout << "constructed" << std::endl;
}
这仍然没有清楚地读取所有内容,并将 b
的初始化与其他内容混为一谈。您可以将 constructor
调用移动到辅助类,代价是空类仍会占用未命名结构中的一些空间(如果它是最后一个成员,通常是一个字节)。
#include <iostream>
#include <cmath>
struct Construct {
template <typename T> Construct(T* instance) {
instance->constructor();
}
};
int main() {
struct {
double a { sqrt(4) };
int b { 42 };
Construct c { this };
void constructor() {
std::cout << "constructed" << std::endl;
}
} instance1, instance2;
}
由于 c
的实例会占用一些空间,我们不妨明确一下,并去掉助手。以下是 C++11 习语的味道,但由于 return 语句而有点冗长。
struct {
double a { sqrt(4) };
int b { 42 };
char constructor { [this]{
std::cout << "constructed" << std::endl;
return char(0);
}() };
}
要获取析构函数,您需要帮助器存储指向包装类实例的指针和指向调用实例析构函数的函数的函数指针。由于我们只能在帮助器的构造函数中访问未命名结构的类型,因此我们必须在那里生成调用析构函数的代码。
#include <iostream>
#include <cmath>
struct ConstructDestruct {
void * m_instance;
void (*m_destructor)(void*);
template <typename T> ConstructDestruct(T* instance) :
m_instance(instance),
m_destructor(+[](void* obj){ static_cast<T*>(obj)->destructor(); })
{
instance->constructor();
}
~ConstructDestruct() {
m_destructor(m_instance);
}
};
int main() {
struct {
double a { sqrt(4) };
int b { 42 };
ConstructDestruct cd { this };
void constructor() {
std::cout << "constructed" << std::endl;
}
void destructor() {
std::cout << "destructed" << std::endl;
}
} instance1, instance2;
std::cout << "body" << std::endl;
}
现在您肯定在提示 ConstructDestruct
实例中存储的数据冗余。存储实例的位置与未命名结构的头部有一个固定的偏移量。您可以获得这样的偏移量并将其包装在一个类型( see here )中。这样我们就可以去掉ConstructorDestructor
中的实例指针:
#include <iostream>
#include <cmath>
#include <cstddef>
template <std::ptrdiff_t> struct MInt {};
struct ConstructDestruct {
void (*m_destructor)(ConstructDestruct*);
template <typename T, std::ptrdiff_t offset>
ConstructDestruct(T* instance, MInt<offset>) :
m_destructor(+[](ConstructDestruct* self){
reinterpret_cast<T*>(reinterpret_cast<uintptr_t>(self) - offset)->destructor();
})
{
instance->constructor();
}
~ConstructDestruct() {
m_destructor(this);
}
};
#define offset_to(member)\
(MInt<offsetof(std::remove_reference<decltype(*this)>::type, member)>())
int main() {
struct {
double a { sqrt(4) };
int b { 42 };
ConstructDestruct cd { this, offset_to(cd) };
void constructor() {
std::cout << "constructed " << std::hex << (void*)this << std::endl;
}
void destructor() {
std::cout << "destructed " << std::hex << (void*)this << std::endl;
}
} instance1, instance2;
std::cout << "body" << std::endl;
}
不幸的是,似乎不可能从 ConstructDestruct
中删除函数指针。不过,这并没有那么糟糕,因为它的大小必须是非零的。无论如何,在未命名结构之后立即存储的任何内容都可能与函数指针大小的倍数对齐,因此 sizeof(ConstructDestruct)
大于 1 可能没有开销。
关于c++ - 如何将构造函数/析构函数添加到未命名的类?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21894450/