samedi 20 octobre 2018

Named Parameter Idiom and (abstract) base classes

Suppose I'm writing a 3D renderer in C++11, where I create materials and assign them to a model.

I'd like to be able to create materials using the Named Parameter Idiom, like this: auto material = Material().color( 1, 0, 0 ).shininess( 200 );

I can then create a model like this: auto model = Model( "path/to/model.obj", material );

I also want to be able to pass the material as an r-value: auto model = Model( "path/to/model.obj", Material() );

In this simple use case, I can write my Model class like this:

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

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

Question

I want to make Material an abstract base class, so that I can create custom implementations for specific kinds of materials. This means that in my Model I must store a pointer (or even reference) to the material instead. But then I can no longer pass the material as an r-value.

I could choose to use a std::shared_ptr<Material> member variable instead, but then it becomes much harder to use the Named Parameter Idiom, because how would I construct the material in that case?

Does any of you have some good advice for me?

More Detailed Example Code

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

Aucun commentaire:

Enregistrer un commentaire