在构建类模板时,通常需要将整个模板放在头文件中,这是不好的,因为严重模板的代码很难解释,编译和生成低于标准的错误消息需要很长时间。但是,通常情况下,模板类中与模板直接相关的部分--参数可以被分离成几个小函数,其余代码可以是常规代码,在需要时调用这些函数:
#include <cstddef>
template <class T>
struct Array {
std::byte* data;
std::size_t size;
std::size_t capacity;
void construct_at(void* p); //
void destruct_at(void* p); // Directly type-related
size_t obj_size(); //
void push_back() { //
if (size == capacity) { //
// ... resize ... //
} //
construct_at(data + size * obj_size()); //
++size; // Not directly
} // type related
//
void pop_back() { //
auto const off = (size - 1) * obj_size(); //
destruct_at(data + off); //
--size; //
} //
};有没有一种方法可以将这个类中与类型(push_back、pop_back)不直接相关的部分提取到非模板代码中,这些代码碰巧调用模板化函数(construct_at、destruct_at、obj_size),这些函数以某种方式对当前的类型执行正确的操作?
理论上,这可以通过虚拟函数来实现:
#include <cstddef>
struct ArrayBase {
std::byte* data;
std::size_t size;
std::size_t capacity;
virtual void construct_at(void* p) = 0; //
virtual void destruct_at(void* p) = 0; // pure-virtual
virtual size_t obj_size() = 0; //
void push_back(); //
void pop_back(); // defined in a cpp file
};
template <class T>
struct Array : ArrayBase {
void construct_at(void* p) override final {
new (p) T;
}
void destruct_at(void* p) override final {
static_cast<T*>(p)->~T();
}
size_t obj_size() override final {
return sizeof(T);
}
};这里的主要问题是,我们需要通过一个虚拟表。但是在我们的代码中,没有运行时的动态性,应该可以避免这种间接性。是这样吗?
发布于 2022-05-31 12:14:05
有一种解决方案不是很通用,但有时适用于创建一个直接与字节一起工作的类的非类型安全版本,然后创建一个环绕复杂逻辑的类型安全版本:
#include <cstddef>
struct ArrayBase {
private:
std::byte* data;
std::size_t size_in_byes;
std::size_t capacity_in_bytes;
protected:
void* push_back(size_t bytes); // Complicated logic is
void* pop_back(size_t bytes); // moved to a .cpp file
};
template <class T>
struct Array : private ArrayBase {
void push_back() {
auto p = push_back(sizeof(T));
new (p) T;
}
void pop_back() {
auto p = pop_back(sizeof(T));
static_cast<T*>(p)->~T();
}
};如果非类型安全版本比类型安全版本要复杂得多,这是可行的.
发布于 2022-05-31 14:33:15
如果我对您的理解是正确的,那么您就会怀疑ArrayBase::push_back()是否能够避免通过虚拟表调用Array::construct_at()。但是,如果目标是减少标题的大小并提高编译时间,那么这是一个明显的否定,除非您使用LTO (链接时间优化)。
如果您在它自己的编译单元中有push_back(),并且只编译它一次,那么编译器就无法知道在编译push_back()时调用哪个版本的construct_at是正确的。编译器也无法在使用push_back()并知道T类型时内联对它的调用。
这方面的例外是当您使用LTO时,因为那时整个源基本上在链接时间变成一个编译单元,所有类型的程序优化都可能发生,包括将虚拟函数调用演绎和消除为静态调用(或插入所述调用)。
如果将整个代码留在标头中,则类似。但是如果分割出非模板的部分又有什么意义呢?
发布于 2022-05-31 11:16:12
一种更简单的提取方法是当方法实际上与模板无关时。
您可以尝试以多种方式删除依赖项以进行键入。
正如您所做的那样,virtual或尝试函数指针,类似于:
#include <cstddef>
struct ArrayBase {
std::byte* data;
std::size_t size;
std::size_t capacity;
#define OPTION moreReadableOption
#if OPTION == 1
void push_back(void (*construct_at)(void*));
void pop_back(void (*destruct_at)(void*));
#elif OPTION == 2
void push_back(std::type_identity_t<void(void*)>* construct_at);
void pop_back(std::type_identity_t<void(void*)>* destruct_at);
else
using construct_at_func = void(void*);
using destruct_at_func = void(void*);
void push_back(construct_at_func* construct_at);
void pop_back(destruct_at_func* destruct_at);
#endif
};
template <class T>
struct Array : ArrayBase {
static void construct_at(void* p) { new (p) T; }
static void destruct_at(void* p) { static_cast<T*>(p)->~T(); }
static size_t obj_size() { return sizeof(T); }
void push_back() { ArrayBase::push_back(&construct_at); }
void pop_back() { ArrayBase::pop_back(&destruct_at); }
};cpp文件:
void ArrayBase::push_back(void (*construct_at)(void*))
{
if (size == capacity) {
// ... resize ...
}
construct_at(data + size * obj_size());
++size;
}
void ArrayBase::pop_back(void (*destruct_at)(void*)) {
auto const off = (size - 1) * obj_size();
destruct_at(data + off);
--size;
}https://stackoverflow.com/questions/72444273
复制相似问题