命名参数成语和(抽象)基类

时间:2018-10-20 09:01:32

标签: c++ c++11 rvalue named-parameters abstract-base-class

假设我正在用C ++ 11编写3D渲染器,在其中创建材质并将其分配给模型。

我希望能够使用命名参数惯用法来创建材质,如下所示:

auto material = Material().color( 1, 0, 0 ).shininess( 200 );

然后我可以创建一个这样的模型:

auto model = Model( "path/to/model.obj", material );

我还希望能够将材质作为r值传递

auto model = Model( "path/to/model.obj", Material() );

在这个简单的用例中,我可以这样编写我的Model类:

class Model {
  public:
    Model() = default;
    Model( const fs::path &path, const Material &material )
      : mPath( path ), mMaterial( material ) {}

  private:
    fs::path mPath;
    Material mMaterial;
};

问题

我想使Material为抽象基类,以便为特定种类的材料创建自定义实现。这意味着我必须在Model中存储指向该材料的指针(甚至引用)。但是后来我无法再将材质作为r值传递。

我可以选择使用std::shared_ptr<Material>成员变量,但是使用命名参数习语变得更加困难,因为在这种情况下我将如何构造材料?

你们中的任何一个对我有很好的建议吗?

更详细的示例代码

class Material {
  public:
    virtual ~Material() = default;
    virtual void generateShader() = 0;
    virtual void bindShader() = 0;
};

class BasicMaterial : public Material {
  public:
    BasicMaterial() = default;
    BasicMaterial( const BasicMaterial &rhs ) = default;

    static std::shared_ptr<BasicMaterial> create( const BasicMaterial &material ) {
      return std::make_shared<BasicMaterial>( material );
    }

    void generateShader() override;
    void bindShader() override;

    BasicMaterial& color( float r, float g, float b ) {
      mColor.set( r, g, b );
      return *this;
    }

  private:
    Color mColor;
};

class Model {
  public:
    Model() = default;
    Model( const fs::path &path, ...what to use to pass Material?... )
      : mPath( path ), mMaterial( material ) {}

  private:
    Material  mMaterial; // Error! Can't use abstract base class as a member.
    Material* mMaterial; // We don't own. What if Material is destroyed?
    Material& mMaterial; // Same question. Worse: can't reassign.

    std::shared_ptr<Material> mMaterial; // This? But how to construct?    
};

// Can't say I like this syntax much. Feels wordy and inefficient.
std::shared_ptr<Material> material = 
  BasicMaterial::create( BasicMaterial().color( 1, 0, 0 ) );

auto model = Model( "path/to/model.obj", 
  BasicMaterial::create( BasicMaterial().color( 0, 1, 0 ) );

2 个答案:

答案 0 :(得分:1)

另一种方法是添加一个MaterialHolder作为成员的类shared_ptr

// *** Not tested ***
class MaterialHolder
{
public:
    template <typename T> T &create() 
{ 
        T *data = new T;
        material.reset(data); 
        return *data; 
}

private:
    std::shared_ptr<Material> material;
};

用法:

MaterialHolder x;
x.create<BasicMaterial>().color(1, 0, 0);

您可以进行一些更改,例如:

  • 具有NullMaterial来处理未调用create的情况。
  • 如果尝试使用未创建的材料,则引发异常。
  • MaterialHolder重命名为MaterialFactory,并仅将该类用于创建目的(并具有移出指针的功能)。
  • 改为使用unique_ptr

或者您也可以随时手动编写代码:

auto m1 = std::make_shared<BasicMaterial>();

(*m1)
    .color(1, 0, 0)
    .shininess(200)
    ;

答案 1 :(得分:0)

实际上,我认为我在详细的代码示例中几乎回答了我自己的问题。 shared_ptr<Material>方法除其构造外,效果很好。但是我可以编写以下辅助函数来对此进行补救:

namespace render {

template<class T>
std::shared_ptr<T> create( const T& inst ) {
  return std::make_shared<T>( inst );
}

}

现在我可以创建这样的材料和模型:

auto model = Model( "path/to/model.obj", create( BasicMaterial().color( 0, 0, 1 ) );

这是可读,清晰且不太冗长的内容。

当然,我仍然很想听听其他想法或意见。