Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using a temporary to initialize multiple members

In older C++98, I don't believe there was any good way to reuse a temporary result in an initializer list to initialize multiple members of an object.

Has this changed at all in newer versions of C++ (11, 14, 17)?

Consider the following code:

//
// compileShaders()
//
// Takes a string containing the source code to possibly hundreds of shaders 
// for an effect (In this case, there are only 2 shaders for this effect)
//
// Returns a vector of compiled bytecode for each shader in the effect
//
std::vector<std::string> compileShaders(
    const std::string& sourceCode, 
    const RenderTargetLayout& layout);

//
// class ScaleDownEffect
//
// Scales image to 1/4 in each dimension by using two 1/2 passes
//
class ScaleDownEffect
{
public:
    ScaleDownEffect(const TargetImage& targetImage)
        :   m_renderTarget(targetImage),

            m_firstPassShader(      
                compileShaders(
                    ScaleDownEffectShaderSource, 
                    m_renderTarget.getLayout()
                    )[0], 
                m_renderTarget.getWidth()  / 2, 
                m_renderTarget.getHeight() / 2
                ),

            m_secondPassShader(     
                compileShaders(
                    ScaleDownEffectShaderSource, 
                    m_renderTarget.getLayout()
                    )[1], 
                m_renderTarget.getWidth()  / 4, 
                m_renderTarget.getHeight() / 4
                )
    {
    }

private:
    RenderTarget m_renderTarget;
    Shader m_firstPassShader;
    Shader m_secondPassShader;
};

compileShaders() is a very heavyweight call, and I don't really think it's a good idea to call it twice when I don't really have to.

Note None of the three objects being initialized have default ctors, so doing it in the body of the function is not really an option.

What do we think? Is there any good way for me to do this, or should I switch to smart pointers and dynamically allocate the objects I contain?

like image 879
something_clever Avatar asked Jan 19 '18 16:01

something_clever


2 Answers

You can use delegating constructor since C++11:

class ScaleDownEffect
{
     ScaleDownEffect(const TargetImage& targetImage,
                     const std::vector<std::string>& shaders)
     : m_renderTarget(targetImage),
       m_firstPassShader(shaders[0],
                         m_renderTarget.getWidth()  / 2,
                         m_renderTarget.getHeight() / 2),
       m_secondPassShader(shaders[1],
                          m_renderTarget.getWidth() / 4,
                          m_renderTarget.getHeight() / 4)
     {}

public:
    ScaleDownEffect(const TargetImage& targetImage)
        : ScaleDownEffect(targetImage,
                          compileShaders(ScaleDownEffectShaderSource,
                                         targetImage.getLayout())) 
    {
    }

private:
    RenderTarget m_renderTarget;
    Shader m_firstPassShader;
    Shader m_secondPassShader;
};
like image 71
Jarod42 Avatar answered Oct 09 '22 10:10

Jarod42


You can use a nested type:

class ScaleDownEffect
{
    struct Shaders
    {
        explicit Shaders(const std::vector<std::string>& s, unsigned int w, unsigned int h)
            : m_firstPassShader(      
                s[0], 
                w  / 2, 
                h / 2
                ),
              m_secondPassShader(     
                s[1], 
                w  / 4, 
                h / 4
                )
        {}

        Shader m_firstPassShader;
        Shader m_secondPassShader;
    };
    RenderTarget m_renderTarget;
    Shaders m_shaders;

public:
    ScaleDownEffect(const TargetImage& targetImage)
    :   m_renderTarget(targetImage),
    ,   m_shaders(compileShaders(ScaleDownEffectShaderSource,
                                     m_renderTarget.getLayout()),
                  m_renderTarget.getWidth(),
                  m_renderTarget.getHeight())
    {
    }
};

You could also pass a reference to m_renderTarget to the nested type's c'tor instead of width and height (which I just assumed to be of type unsigned int, btw).

There is one disadvantage in that you now need to access the first pass shader e.g. as m_shaders.m_firstPassShader. I recommend adding (inline) getter methods, even if they return references, and even if they are private, to isolate code using the shaders from changes in the way they are stored.

like image 23
Arne Vogel Avatar answered Oct 09 '22 10:10

Arne Vogel