Thread: Best place to forward declare PIMPL class?

  1. #1
    Registered User
    Join Date
    Apr 2010
    Posts
    88

    Best place to forward declare PIMPL class?

    Hello,

    I'm just wondering what is considered to be the best practice with respect to a pimpl class. Just for context, with the PIMPL idiom, one often uses a pointer to an implementation class to obscure the implementation of a class, reduce re-compile time, etc.

    For example, here is what the class header might look like, "a.hpp"

    Code:
    class A_impl;
    
    class A{
    private:
      A_impl * pimpl;
    };
    Or, the forward declaration could be done within the class?

    Code:
    class A{
    private:
      class A_impl;
      A_impl * pimpl;
    };
    What is considered the best way to go? I'm guessing within to avoid polluting the containing namespace?
    W7, Ubuntu -- mingw, gcc, g++, code::blocks, emacs, notepad++

  2. #2
    Registered User
    Join Date
    Jun 2014
    Posts
    66
    Hey,

    I'm not really an expert but have you considered the following:

    Code:
    class test {
        class implementation;
        
    public:
        test()
            : m_detail{new implementation}
        {
        }
        
    private:
        std::unique_ptr<implementation> m_detail;
        
        class implementation { // won't compile without this
        };
    };
    As you can see, the `test' class won't compile without having the class `implementation' defined in its scope (try it if unsure: defining it at global scope won't work either). Now to the problem: how does one define the `implementation' class inside the scope of `test' from another file? As far as I know, this isn't possible and therefore PIMPL can't work this way since it's about hiding the implementation from the public class interface...

    For this reason I'd rather stick to the first option Good luck!
    Last edited by milli-961227; 07-14-2015 at 04:53 PM.

  3. #3
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    It is very much possible:

    Code:
    // Header
    class foo
    {
    public:
    	foo(); // Forward declare this
    	foo(const foo&); // Forward declare this
    	foo(foo&& that) = default;
    	void operator = (const foo&) = delete; // Or forward declare and implement
    	foo& operator = (foo&& that) = default;
    	~foo(); // Forward declare this
    
    private:
    	struct impl;
    	impl* m_impl;
    };
    
    // Source file
    struct foo::impl
    {
    	// Implementation details here...
    };
    
    foo::foo():
    	m_impl(new impl())
    {}
    
    foo::foo(const foo& that):
    	m_impl( new impl(*that.m_impl) )
    {}
    
    foo::~foo()
    {
    	delete m_impl;
    }
    Anyway, I'd say go for within the class to avoid polluting the global namespace.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  4. #4
    Registered User
    Join Date
    Apr 2010
    Posts
    88
    Thank you both for the replies.

    First, to milli, I think the issue you came across is that std::unique_ptr causes problems if you don't declare a destructor. I forward declared the implementation class within the user-visible class, and then fully defined it in the corresponding .cpp file. All I had to do to get it to compile was to ensure I had a destructor (empty-body sufficed). Thank you for replying, though, as it reminded me that it's better to set up my work with smart pointers.

    I have a follow up question. I understand the appeal of not contaminating namespaces. I've read on Herb Sutter's as well as other quick tutorial pages that forward declaring within the visible class is more "professional" but that it has drawbacks. However, I have not been able to find elaboration on what these drawbacks are.

    For example, this article:

    The C++ Pimpl - General Programming - Articles - Articles - GameDev.net

    It states:

    The Pimpl idiom pollutes the namespace with another class. If you want to avoid that, you can implement the private implementation class as a local class instead. However, do note that local classes have many restrictions.
    Any examples of these restrictions?
    Last edited by Ocifer; 07-15-2015 at 12:01 AM.
    W7, Ubuntu -- mingw, gcc, g++, code::blocks, emacs, notepad++

  5. #5
    Registered User
    Join Date
    Jun 2014
    Posts
    66
    Quote Originally Posted by Elysia View Post
    It is very much possible:

    (...)

    Anyway, I'd say go for within the class to avoid polluting the global namespace.
    Thank you Elysia, I haven't thought it was possible, but obviously I was wrong. It's always good to learn something new; I'll keep this in mind for the future.
    Last edited by milli-961227; 07-15-2015 at 04:32 AM.

  6. #6
    [](){}(); manasij7479's Avatar
    Join Date
    Feb 2011
    Location
    *nullptr
    Posts
    2,657
    Another alternative to consider...

    I have seen code just puts a void* in the header.

    Then in your implementation, you can have something like
    Code:
    #define IMPL static_cast<Impl*>(pimpl)
    Whether that is advisable, I don't know, but then..I am not a fan of this idiom at all.

    But it certainly makes your class look a little bit cleaner.

  7. #7
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Quote Originally Posted by manasij7479 View Post
    I have seen code just puts a void* in the header.

    Then in your implementation, you can have something like
    Code:
    #define IMPL static_cast<Impl*>(pimpl)
    That's a horrible hack, if you ask me. Not to mention it uses a macro to overcome its shortcoming. Then what's the point of doing it in the first place if just forward declaring an incomplete type allows you to do this without ugly hacks?

    I am not a fan of this idiom at all.
    I am a fan of it since it fixes C++'s glaring problem with exposing implementation details in the header, and because it cuts down on compilation times every time you need to change some implementation stuff.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  8. #8
    [](){}(); manasij7479's Avatar
    Join Date
    Feb 2011
    Location
    *nullptr
    Posts
    2,657
    Quote Originally Posted by Elysia View Post
    I am a fan of it since it fixes C++'s glaring problem with exposing implementation details in the header, and because it cuts down on compilation times every time you need to change some implementation stuff.
    All that and more, until you have templates.

  9. #9
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    I am a fan of it since it fixes C++'s glaring problem with exposing implementation details in the header, and because it cuts down on compilation times every time you need to change some implementation stuff.
    O_o

    The "Cheshire Cat" idiom isn't necessary to hide implementation details.

    Code:
    std::unique_ptr<Interface> s(CraftInstance());
    The approach returning a smart pointer also saves header dependencies.

    The "Cheshire Cat" idiom only adds a Bridge over the `Interface` class and factory so you can pretend to have a value instead of a reference.

    All that and more, until you have templates.
    I'll see your templates and raise you erasure.

    The dependencies on templates don't have to be exposed to clients.

    You can even erase the `std::unique_ptr<???>`, as in the example posted by milli-961227, if you want.

    Soma
    “Salem Was Wrong!” -- Pedant Necromancer
    “Four isn't random!” -- Gibbering Mouther

  10. #10
    Registered User
    Join Date
    Dec 2013
    Posts
    241
    As a phlisophical question :
    in this PIMPL implementation , we basically converting compile time into run time. why? because if we didn't PIMPL, the compiler would include the inner implementation, then , on runtime - the whole class is "complete" andif you stack-declare the object everything will be declared in the stack in contiguous order.
    When you PIMPL - you provide heap-allocated memory address as sub-object. even if you stack allocate the object - the program still have to search over the heap for available space - thus increasing the runtime.
    isn't it the whole point of why C++ is so fast? we declare most of our object on the stack, we try dynamically allocate things as less as we can and yes- we basically sitting in front of the compiler for some time in order that the end user will expirience as much performance as possible.

    this basically do the opposite, the developer says he don't care about runtime, but he want to save compile time. do we really consider this favorable?
    don't get me wrong. allocate data from the heap is also fast, no-one will even notice teh change, but still, this is a philosophical question I'd like to hear you answers

  11. #11
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Quote Originally Posted by Dave11 View Post
    When you PIMPL - you provide heap-allocated memory address as sub-object. even if you stack allocate the object - the program still have to search over the heap for available space - thus increasing the runtime.
    You don't actually have to do that. You can create a "storage" array as part of your public interface where you can construct your impl object:

    char storage[1024];
    new (storage) impl(...);

    Of course, the downside is that if you need to change the storage size, you need to change the header, which causes a recompilation of all files depending the header.

    isn't it the whole point of why C++ is so fast? we declare most of our object on the stack, we try dynamically allocate things as less as we can and yes- we basically sitting in front of the compiler for some time in order that the end user will expirience as much performance as possible.

    this basically do the opposite, the developer says he don't care about runtime, but he want to save compile time. do we really consider this favorable?
    You tell me which is favorable: saving minutes, or perhaps even hours, of compile time for, maybe, a few seconds of extra runtime overhead?
    Big projects can take hours to compile. Therefore many developers try to minimize dependencies such that if you change one file, another does not need to recompile. Do these changes come with extra runtime overhead? Often, yes. But it's worth it to save the compile time.
    You also have to consider premature optimization. If the code in question is called less than 0.01% and changing it can cause recompilation that takes hours, is it worth it to use pimpl? Everything comes with advantages and disadvantages and pimpl is no exception. We're trading compile time for extra runtime overhead in many cases (or increased memory consumption). Sometimes this is desirable, sometimes not. It all depends on the situation.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  12. #12
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    in this PIMPL implementation , we basically converting compile time into run time. why?
    O_o

    I'm fine with what Elysia had to say. The supposed costs though of correct, reliable, and clean code is an issue with me thanks to repeated cowboy exposure to over the years so I decided to put my own spin on what is in some ways the same arguments.

    You asked about two aspects, but what about reliability?

    Even in C++98 code, you want to be able to perform certain operations without risking the possibility of failure. A primitive that doesn't provide certain exception guarantees is difficult to make exception safe and impossible to make exception neutral. In order to make such a guarantee for some operations, you need to be able to attempt the operation before then committing results on success without then introducing a possible point of failure.

    Code:
    class SWhatever
    {
        // ...
        void doSomething(/* ... */)
        {
            mData1 = GetUsefulData1(/* ... */);
            mData2 = GetUsefulData2(/* ... */);
            // ...
        }
        SData1 mData1;
        SData2 mData2;
    };
    Let's assume the assignments can throw an exception. As is, the code appears to provide at least a weak guarantee. In reality, we don't know that `mData1` and `mData2` doesn't have some interrelationship which the `SWhatever` class is packaging. We need at least the weak guarantee for clients to be able to make any guarantees. We are assuming the assignment can throw an exception so we can't just swap back and forth using a temporary. (The default behavior of `std::swap` is using a temporary for storage.) We need a means to treat the two assignments as if they were a single atomic operation. You might try some `catch` nonsense, or you might try moving the two members into a separate class, but you'll find that neither layers of `catch` blocks or moving the members into a separate class will buy anything worth having developed. We need a way to say "copy this data without the possibility of failure", but we can never implement such a beast by somehow chaining operations which may fail. In order to buy what we want, we must somewhere use an operation which can't fail. Outside of trivial classes, the only assignment operations which can't fail are exclusive to native types. Handily, the pointers are native types.

    Code:
    class SWhatever
    {
        // ...
        void doSomething(/* ... */)
        {
            SData1 * sData1(0);
            SData2 * sData2(0);
            try
            {
                sData1 = new SData1(GetUsefulData1(/* ... */));
                sData2 = new SData2(GetUsefulData2(/* ... */));
                // If we got to this point, we know the assignment and allocation was successful.
                {
                    SData1 * sTemporary1(mData1);
                    SData2 * sTemporary2(mData2);
                    mData1 = sData1;
                    mData2 = sData2;
                    delete sTemporary1;
                    delete sTemporary2;
                }
            }
            catch(...)
            {
                delete sData1;
                delete sData2;
                throw;
            }
            // ...
        }
        SData1 * mData1;
        SData2 * mData2;
    };
    Yuck. The code isn't exception neutral, has several bits of duplication, potentially wastes time allocating an object which can't be used. The code has a lot has a lot of problems, but the `doSomething` method is reliable. Clients can use the `doSomething` method without worrying if a problem will leave them holding an object which isn't useful.

    Now, let's use some standard primitives so we don't have such problems.

    Code:
    class SWhatever
    {
        // ...
        void doSomething(/* ... */)
        {
            std::unique_ptr<SData1> mData1(new SData1(GetUsefulData1(/* ... */)));
            std::unique_ptr<SData2> mData2(new SData2(GetUsefulData2(/* ... */)));
            // If we got to this point, we know the assignment and allocation was successful.
            mData1.swap(mData1);
            mData2.swap(mData2);
        }
        std::unique_ptr<SData1> mData1;
        std::unique_ptr<SData2> mData2;
    };
    Yay. We have some pretty clean code while still implementing a reliable method.

    Of course, the `SWhatever` method is most likely going to need to implement more than one method operating on both members. We don't like repetition so we can package the bits into an interface making the use a little more palatable, and we even have a performance related excuse because two allocations probably always cost more than one allocation.

    Code:
    namespace Detail
    {
        class SWhateverData
        {
            SWhateverData
            (
                const SData1 & fData1
              , const SData2 & fData2
            ):
                mData1(fData1)
              , mData2(fData2)
            {
            }
            SData1 mData1;
            SData2 mData2;
        };
    }
    class SWhatever
    {
        // ...
        void doSomething(/* ... */)
        {
            mData = new Detail::SWhateverData(GetUsefulData1(/* ... */), GetUsefulData2(/* ... */));
        }
        std::unique_ptr<Detail::SWhateverData> mData;
    };
    You'll find that implementing such behaviors, with some mechanism, is very common in the C++ language. The tools of idiomatic C++ demands making some guarantees to clients which sometimes costs an allocation, developing indirect layers, or even just some tedious hoop jumpery. The execution and development costs of then layering the "Cheshire Cat" idiom are trivial. We are almost at the "Cheshire Cat" level just by virtue of writing a robust method with idiomatic C++ code.

    Soma
    “Salem Was Wrong!” -- Pedant Necromancer
    “Four isn't random!” -- Gibbering Mouther

  13. #13
    Registered User MutantJohn's Avatar
    Join Date
    Feb 2013
    Posts
    2,665
    So wait, what's the differnece with PIMPL and just declaring in headers and defining in source files?

  14. #14
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    You hide the private members of the class:

    Code:
    class foo
    {
    public:
    // My public stuff
    
    private:
    class impl;
    impl* m_impl; // All private members are hidden in foo::impl
    };
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  15. #15
    Registered User MutantJohn's Avatar
    Join Date
    Feb 2013
    Posts
    2,665
    That sounds kind of underwhelming O_o

    I only mention it looks similar because I was reading the opaque pointer wiki and it didn't really look all that different from what I normally do.

    Are there other practical reasons that separate this from just declaring in a header file and then implementing in a source file?

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. GCC lets you forward declare a struct with "class /name/"
    By Mozza314 in forum C++ Programming
    Replies: 12
    Last Post: 02-20-2011, 04:52 AM
  2. Understanding forward declarations of classes, pimpl
    By Boxknife in forum C++ Programming
    Replies: 2
    Last Post: 04-22-2010, 01:29 AM
  3. forward class declarations
    By manzoor in forum C++ Programming
    Replies: 17
    Last Post: 12-05-2008, 03:55 AM
  4. Replies: 5
    Last Post: 04-17-2008, 02:31 AM
  5. Forward Declaration of Class Members?
    By 691175002 in forum C++ Programming
    Replies: 3
    Last Post: 01-17-2008, 10:34 PM