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 函数调用以外的所有内容,并具有以下限制。

  1. 我无法修改图形模板。否则我会简单地将 Vertex 类型移动到联合中。
  2. 我不能使用 std::variant 因为 graph<T> 不能默认构造。
  3. 通话开销是个问题。如果有基于子类型多态性的解决方案不涉及使 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除外)。

一个解决方案是在阅读 compressedsymmetric 后立即执行此转换一次,并从那里保持完全模板化:

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

Demo

您可能需要编写很多元函数(例如 isCompressed),但可以正常编码(尽管您的 IDE 对您帮助不大)。你没有以任何方式被锁定。