non-default 可构建的 class 上的运行时类型解析,没有多态性
Runtime type resolution on a non-default constructible class without polymorphysm
我有点困惑。我有一个带有模板参数的模板 class graph
- class vertex
,它可以是对称的或不对称的,压缩的或原始的,我只知道在 运行时间。
因此,如果我想从磁盘上获取适当类型的图形,运行 Bellman Ford 在其上然后释放内存,我需要在所有四个条件分支中重复模板实例化,例如所以:
#include "graph.h"
int main(){
// parse cmd-line args, to get `compressed` `symmetric`
// TODO get rid of conditionals.
if (compressed) {
if (symmetric) {
graph<compressedSymmetricVertex> G =
readCompressedGraph<compressedSymmetricVertex>(iFile, symmetric,mmap);
bellman_ford(G,P);
} else {
graph<compressedAsymmetricVertex> G =
readCompressedGraph<compressedAsymmetricVertex>(iFile,symmetric,mmap);
bellman_ford(G,P);
if(G.transposed) G.transpose();
G.del();
}
} else {
if (symmetric) {
graph<symmetricVertex> G =
readGraph<symmetricVertex>(iFile,compressed,symmetric,binary,mmap);
bellman_ford(G,P);
G.del();
} else {
graph<asymmetricVertex> G =
readGraph<asymmetricVertex>(iFile,compressed,symmetric,binary,mmap);
bellman_ford(G,P);
if(G.transposed) G.transpose();
G.del();
}
}
return 0;
}
问题:我如何提取除条件之外的 readGraph
函数调用以外的所有内容,并具有以下限制。
- 我无法修改图形模板。否则我会简单地将 Vertex 类型移动到联合中。
- 我不能使用
std::variant
因为 graph<T>
不能默认构造。
- 通话开销是个问题。如果有基于子类型多态性的解决方案不涉及使
compressedAsymmetricVertex
成为 vertex
的子类型,我洗耳恭听。
编辑:这是一个示例header graph.h
:
#pragma once
template <typename T>
struct graph{ T Data; graph(int a): Data(a) {} };
template <typename T>
graph<T> readGraph<T>(char*, bool, bool, bool) {}
template <typename T>
graph<T> readCompressedGraph<T> (char*, bool, bool) {}
class compressedAsymmetricVertex {};
class compressedSymmetricVertex {};
class symmetricVertex{};
class asymmetricVertex {};
由于你没有把所有的类型都说出来,也没有说明binary
参数是怎么回事,所以我只能给出一个大概的解。根据您的确切需求对其进行优化。这应该符合:
class GraphWorker
{
public:
GraphWorker(bool compressed, bool symmetric)
: m_compressed(compressed), m_symmetric(symmetric)
{}
virtual void work(const PType & P, const char * iFile, bool binary, bool mmap ) const = 0;
protected:
const bool m_compressed;
const bool m_symmetric;
};
template <class GraphType>
class ConcreteGraphWorker : public GraphWorker
{
public:
ConcreteGraphWorker(bool compressed, bool symmetric)
: GraphWorker(compressed, symmetric)
{}
void work(const PType & P, const char * iFile, bool binary, bool mmap) const override
{
graph<GraphType> G =
readGraph<GraphType>(iFile, m_compressed, m_symmetric,
binary, mmap);
bellman_ford(G,P);
G.del();
}
};
static const std::unique_ptr<GraphWorker> workers[2][2] = {
{
std::make_unique<ConcreteGraphWorker<asymmetricVertex>>(false, false),
std::make_unique<ConcreteGraphWorker<symmetricVertex>>(false, true),
},
{
std::make_unique<ConcreteGraphWorker<compressedAsymmetricVertex>>(true, false),
std::make_unique<ConcreteGraphWorker<compressedSymmetricVertex>>(true, true),
}
};
int main()
{
workers[compressed][symmetric]->work(P, iFile, binary, mmap);
}
一些评论: 最好完全避免bool
,并使用特定的枚举类型。这意味着您应该使用类似以下内容的内容,而不是我的二维数组:
std::map<std::pair<Compression, Symmetry>, std::unique_ptr<GraphWorker>> workers;
但由于可能存在其他未知依赖项,我决定坚持使用令人困惑的 bool
变量。此外,将 workers
作为静态变量有其缺点,并且由于我不知道您的其他要求,所以我不知道如何处理它。另一个问题是基数 class 中受保护的布尔变量。通常,我会改用访问器。
我不确定所有这些为了避免几个条件而跳来跳去的过程是否值得。这比原来的代码更长更复杂,除非有超过 4 个选项,或者 work()
中的代码更长,否则我建议坚持使用条件。
编辑: 我刚刚意识到使用 lambda 函数可以说更清晰(有待商榷)。在这里:
int main()
{
using workerType = std::function<void(PType & P, const char *, bool, bool)>;
auto makeWorker = [](bool compressed, bool symmetric, auto *nullGrpah)
{
auto worker = [=](PType & P, const char *iFile, bool binary, bool mmap)
{
// decltype(*nullGraph) is a reference, std::decay_t fixes that.
using GraphType = std::decay_t<decltype(*nullGrpah)>;
auto G = readGraph<GraphType>(iFile, compressed, symmetric,
binary, mmap);
bellman_ford(G,P);
G.del();
};
return workerType(worker);
};
workerType workers[2][2] {
{
makeWorker(false, false, (asymmetricVertex*)nullptr),
makeWorker(false, true, (symmetricVertex*)nullptr)
},
{
makeWorker(true, false, (compressedAsymmetricVertex*)nullptr),
makeWorker(true, true, (compressedSymmetricVertex*)nullptr)
}
};
workers[compressed][symmetric](P, iFile, binary, mmap);
}
简单的基准是,无论何时你想从 "type only known at runtime" 跨越到 "type must be known at compile-time"(即模板),你都需要一系列这样的条件。如果您根本无法修改 graph
,那么每当您想要处理 中的 G
对象时,您将需要四个不同的 G
变量(和分支)非模板函数,因为所有graph
模板变体都是不相关的类型,不能统一对待(std::variant
除外)。
一个解决方案是在阅读 compressed
和 symmetric
后立即执行此转换一次,并从那里保持完全模板化:
template<class VertexT>
graph<VertexT> readTypedGraph()
{
if constexpr (isCompressed<VertexT>::value)
return readCompressedGraph<VertexT>(/*...*/);
else
return readGraph<VertexT>(/*...*/);
}
template<class VertexT>
void main_T()
{
// From now on you are fully compile-time type-informed.
graph<VertexT> G = readTypedGraph<VertexT>();
bellman_ford(G);
transposeGraphIfTransposed(G);
G.del();
}
// non-template main
int main()
{
// Read parameters.
bool compressed = true;
bool symmetric = false;
// Switch to fully-templated code.
if (compressed)
if (symmetric)
main_T<compressedSymmetricVertex>();
else
main_T<compressedAsymmetricVertex>();
// else
// etc.
return 0;
}
您可能需要编写很多元函数(例如 isCompressed
),但可以正常编码(尽管您的 IDE 对您帮助不大)。你没有以任何方式被锁定。
我有点困惑。我有一个带有模板参数的模板 class graph
- class vertex
,它可以是对称的或不对称的,压缩的或原始的,我只知道在 运行时间。
因此,如果我想从磁盘上获取适当类型的图形,运行 Bellman Ford 在其上然后释放内存,我需要在所有四个条件分支中重复模板实例化,例如所以:
#include "graph.h"
int main(){
// parse cmd-line args, to get `compressed` `symmetric`
// TODO get rid of conditionals.
if (compressed) {
if (symmetric) {
graph<compressedSymmetricVertex> G =
readCompressedGraph<compressedSymmetricVertex>(iFile, symmetric,mmap);
bellman_ford(G,P);
} else {
graph<compressedAsymmetricVertex> G =
readCompressedGraph<compressedAsymmetricVertex>(iFile,symmetric,mmap);
bellman_ford(G,P);
if(G.transposed) G.transpose();
G.del();
}
} else {
if (symmetric) {
graph<symmetricVertex> G =
readGraph<symmetricVertex>(iFile,compressed,symmetric,binary,mmap);
bellman_ford(G,P);
G.del();
} else {
graph<asymmetricVertex> G =
readGraph<asymmetricVertex>(iFile,compressed,symmetric,binary,mmap);
bellman_ford(G,P);
if(G.transposed) G.transpose();
G.del();
}
}
return 0;
}
问题:我如何提取除条件之外的 readGraph
函数调用以外的所有内容,并具有以下限制。
- 我无法修改图形模板。否则我会简单地将 Vertex 类型移动到联合中。
- 我不能使用
std::variant
因为graph<T>
不能默认构造。 - 通话开销是个问题。如果有基于子类型多态性的解决方案不涉及使
compressedAsymmetricVertex
成为vertex
的子类型,我洗耳恭听。
编辑:这是一个示例header graph.h
:
#pragma once
template <typename T>
struct graph{ T Data; graph(int a): Data(a) {} };
template <typename T>
graph<T> readGraph<T>(char*, bool, bool, bool) {}
template <typename T>
graph<T> readCompressedGraph<T> (char*, bool, bool) {}
class compressedAsymmetricVertex {};
class compressedSymmetricVertex {};
class symmetricVertex{};
class asymmetricVertex {};
由于你没有把所有的类型都说出来,也没有说明binary
参数是怎么回事,所以我只能给出一个大概的解。根据您的确切需求对其进行优化。这应该符合:
class GraphWorker
{
public:
GraphWorker(bool compressed, bool symmetric)
: m_compressed(compressed), m_symmetric(symmetric)
{}
virtual void work(const PType & P, const char * iFile, bool binary, bool mmap ) const = 0;
protected:
const bool m_compressed;
const bool m_symmetric;
};
template <class GraphType>
class ConcreteGraphWorker : public GraphWorker
{
public:
ConcreteGraphWorker(bool compressed, bool symmetric)
: GraphWorker(compressed, symmetric)
{}
void work(const PType & P, const char * iFile, bool binary, bool mmap) const override
{
graph<GraphType> G =
readGraph<GraphType>(iFile, m_compressed, m_symmetric,
binary, mmap);
bellman_ford(G,P);
G.del();
}
};
static const std::unique_ptr<GraphWorker> workers[2][2] = {
{
std::make_unique<ConcreteGraphWorker<asymmetricVertex>>(false, false),
std::make_unique<ConcreteGraphWorker<symmetricVertex>>(false, true),
},
{
std::make_unique<ConcreteGraphWorker<compressedAsymmetricVertex>>(true, false),
std::make_unique<ConcreteGraphWorker<compressedSymmetricVertex>>(true, true),
}
};
int main()
{
workers[compressed][symmetric]->work(P, iFile, binary, mmap);
}
一些评论: 最好完全避免bool
,并使用特定的枚举类型。这意味着您应该使用类似以下内容的内容,而不是我的二维数组:
std::map<std::pair<Compression, Symmetry>, std::unique_ptr<GraphWorker>> workers;
但由于可能存在其他未知依赖项,我决定坚持使用令人困惑的 bool
变量。此外,将 workers
作为静态变量有其缺点,并且由于我不知道您的其他要求,所以我不知道如何处理它。另一个问题是基数 class 中受保护的布尔变量。通常,我会改用访问器。
我不确定所有这些为了避免几个条件而跳来跳去的过程是否值得。这比原来的代码更长更复杂,除非有超过 4 个选项,或者 work()
中的代码更长,否则我建议坚持使用条件。
编辑: 我刚刚意识到使用 lambda 函数可以说更清晰(有待商榷)。在这里:
int main()
{
using workerType = std::function<void(PType & P, const char *, bool, bool)>;
auto makeWorker = [](bool compressed, bool symmetric, auto *nullGrpah)
{
auto worker = [=](PType & P, const char *iFile, bool binary, bool mmap)
{
// decltype(*nullGraph) is a reference, std::decay_t fixes that.
using GraphType = std::decay_t<decltype(*nullGrpah)>;
auto G = readGraph<GraphType>(iFile, compressed, symmetric,
binary, mmap);
bellman_ford(G,P);
G.del();
};
return workerType(worker);
};
workerType workers[2][2] {
{
makeWorker(false, false, (asymmetricVertex*)nullptr),
makeWorker(false, true, (symmetricVertex*)nullptr)
},
{
makeWorker(true, false, (compressedAsymmetricVertex*)nullptr),
makeWorker(true, true, (compressedSymmetricVertex*)nullptr)
}
};
workers[compressed][symmetric](P, iFile, binary, mmap);
}
简单的基准是,无论何时你想从 "type only known at runtime" 跨越到 "type must be known at compile-time"(即模板),你都需要一系列这样的条件。如果您根本无法修改 graph
,那么每当您想要处理 中的 G
对象时,您将需要四个不同的 G
变量(和分支)非模板函数,因为所有graph
模板变体都是不相关的类型,不能统一对待(std::variant
除外)。
一个解决方案是在阅读 compressed
和 symmetric
后立即执行此转换一次,并从那里保持完全模板化:
template<class VertexT>
graph<VertexT> readTypedGraph()
{
if constexpr (isCompressed<VertexT>::value)
return readCompressedGraph<VertexT>(/*...*/);
else
return readGraph<VertexT>(/*...*/);
}
template<class VertexT>
void main_T()
{
// From now on you are fully compile-time type-informed.
graph<VertexT> G = readTypedGraph<VertexT>();
bellman_ford(G);
transposeGraphIfTransposed(G);
G.del();
}
// non-template main
int main()
{
// Read parameters.
bool compressed = true;
bool symmetric = false;
// Switch to fully-templated code.
if (compressed)
if (symmetric)
main_T<compressedSymmetricVertex>();
else
main_T<compressedAsymmetricVertex>();
// else
// etc.
return 0;
}
您可能需要编写很多元函数(例如 isCompressed
),但可以正常编码(尽管您的 IDE 对您帮助不大)。你没有以任何方式被锁定。