6
votes

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?

2
Does shader have a default constructor (ie can it be default constructed, and then assigned in the constructor)? - UKMonkey
Nope - Neither Shader nor RenderTarget have default ctors (And I don't own the code - It's a library from another team) - something_clever
you could write a method that holds the result of the call in a static variable and use that to initialize the members, a bit hackish but works also in pre C++11 - 463035818_is_not_a_number
@tobi303 - I hope it doesn't come down to that, but it is an idea. It might be one of several messy alternatives by-the-looks-of-it. - something_clever
@tobi303: Before doing that, I'd rather save the temporary in an additional private data member and clear it when the constructor has finished. - Christian Hackl

2 Answers

8
votes

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

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.