Nik*_*iou 38 c++ constructor destructor class declaration
有没有办法在未命名的类中声明构造函数或析构函数?考虑以下
void f()
{
struct {
// some implementation
} inst1, inst2;
// f implementation - usage of instances
}
Run Code Online (Sandbox Code Playgroud)
后续问题:实例是基于任何基于堆栈的对象构造(和销毁)的.叫什么?它是由编译器自动分配的错位名称吗?
Rei*_*ica 35
最简单的解决方案是将一个命名的struct实例作为成员放在未命名的实例中,并将所有功能放入命名实例中.这可能是与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;
}
Run Code Online (Sandbox Code Playgroud)
以下所有内容都需要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;
}
Run Code Online (Sandbox Code Playgroud)
您当然可以将所有"构造函数"代码推送到单独的成员:
int b { [this]{ constructor(); return 42; }() };
void constructor() {
std::cout << "constructed" << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
这仍然不能完全读取所有内容,并将初始化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;
}
Run Code Online (Sandbox Code Playgroud)
由于c
将使用一些空间的实例,我们不妨明白它,并摆脱帮助.以下是C++ 11习惯用法的气味,但由于return语句有点冗长.
struct {
double a { sqrt(4) };
int b { 42 };
char constructor { [this]{
std::cout << "constructed" << std::endl;
return char(0);
}() };
}
Run Code Online (Sandbox Code Playgroud)
要获取析构函数,您需要帮助程序来存储指向包装类实例的指针,以及指向调用实例上析构函数的函数的函数指针.因为我们只能在helper的构造函数中访问未命名的struct类型,所以我们必须生成调用析构函数的代码.
#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;
}
Run Code Online (Sandbox Code Playgroud)
现在你肯定在抱怨ConstructDestruct
实例中存储的数据的冗余.存储实例的位置与未命名结构的头部相距固定偏移量.您可以获取此类偏移并将其包装在一个类型中(请参见此处).因此我们可以摆脱以下实例指针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;
}
Run Code Online (Sandbox Code Playgroud)
不幸的是,似乎无法从内部摆脱函数指针ConstructDestruct
.但这并不是那么糟糕,因为它的大小必须非零.无论如何在未命名的struct之后立即存储的内容很可能与函数指针大小的倍数对齐,因此sizeof(ConstructDestruct)
大于1时可能没有开销.