没有多态性的非默认可构造类的运行时类型解析

时间:2018-08-13 14:10:14

标签: c++ code-reuse

我有点困惑。我有一个带有模板参数的模板类graph-类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的子类型,那么我无所不能。

编辑:这是示例标头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 {};

2 个答案:

答案 0 :(得分:2)

由于您没有列出所有类型,也没有说明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作为静态变量也有其缺点,并且由于我不知道您的其他要求,所以我也不知道该怎么做。另一个问题是基类中受保护的布尔变量。通常,我会改用访问器。

我不确定是否为了避免出现一些有条件限制而跳过所有这些花销,是否值得?这比原始代码更长,更棘手,并且除非有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);
}

答案 1 :(得分:1)

简单的基准是,只要您想从“仅在运行时知道的类型”过渡到“在编译时必须知道的类型”(即模板),就需要一系列这样的条件。如果您根本无法修改graph,那么无论何时您想在 non-em>中处理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不能提供太多帮助)。您丝毫没有受到任何限制。