Encapsulation through anonymous namespaces

Started by
23 comments, last by AlanSmithee 9 years, 2 months ago
Well I assume you meant to pass the texture manager instance via reference instead of by value, but yeah, that looks pretty good to me.
Advertisement

Naming conventions aside, I think this is pretty much what Ravyne is talking about?

Any feedback on this approach?

That's pretty good, though (again to push the point I was getting at earlier) you could make it even more encapsulated, at the expense of some extra code:

[source]

// Public interface of texture manager (texture.h)
namespace TextureManager
{
class Instance;
void IAmAPartOfTheTextureManagerInterface(Instance& textureManager);
void DoOtherThingWithTextureManager(Instance& textureManager);
}
// Implementation of public interface and definition of private interface (Texture.cpp)
namespace TextureManager
{
class Instance
{
public:
void ChangeTheStateOfThisInstance()
{
++_secretVariable;
}
void ChangeTheStateOfThisInstanceTwo()
{
--_secretVariable;
}
void ChangeTheStateOfThisInstanceThree()
{
_secretVariable *= _secretVariable;
}
private:
int _secretVariable = 0;
};
void IAmAPartOfTheTextureManagerInterface(Instance textureManager)
{
textureManager.ChangeTheStateOfThisInstance();
textureManager.ChangeTheStateOfThisInstanceTwo();
textureManager.ChangeTheStateOfThisInstanceThree();
}
void DoOtherThingWithTextureManager(Instance& textureManager)
{
textureManager.ChangeTheStateOfThisInstanceTwo();
}
}
// Usage (main.cpp)
int main()
{
TextureManager::Instance textureManager;
TextureManager::IAmAPartOfTheTextureManagerInterface(textureManager);
// and/or
// no longer doing this
//textureManager.ChangeTheStateOfThisInstanceTwo();
// instead do this
DoOtherThingWithTextureManager(textureManager);
...
return 0;
}
[/source]

Now note that only texture.cpp knows the definition of the TextureManager. All anyone else knows about is the functions in that namespace you've provided.

Thanks for your additional replies!

I didn't know that your could forward declear a class like that in the header and leave both declaration and definition of it's body to the source file.. thought that would cause the compiler to throw a multiple declarations error.. cool!

I really like the example in your last post, Oberon_Command. It achives exactly what I wanted to with the functions in the anonymous namespace, but this way I am enable to have multiple instances of a type with a unified API through free functions.

Thanks for your help.

(and yes, I would pass it by ref had I not written the code in the browser and forgotten about it :))


I didn't know that your could forward declear a class like that in the header and leave both declaration and definition of it's body to the source file.. thought that would cause the compiler to throw a multiple declarations error.. cool!

That's actually necessary in some cases. Consider the following examples of circular references:

[source]

struct Foo{

Bar* x; // error, Bar is not declared

};

struct Wololo;

struct Bar {

Wololo* x; // no error, Wololo was forward declared

};

struct Wololo {

Bar* x; // no error, we already declared Bar above

};

[/source]


I didn't know that your could forward declear a class like that in the header and leave both declaration and definition of it's body to the source file.. thought that would cause the compiler to throw a multiple declarations error.. cool!

That's actually necessary in some cases. Consider the following examples of circular references:

[source]

struct Foo{

Bar* x; // error, Bar is not declared

};

struct Wololo;

struct Bar {

Wololo* x; // no error, Wololo was forward declared

};

struct Wololo {

Bar* x; // no error, we already declared Bar above

};

[/source]

Yeah, I know about foward declaration, but I thought you had to re-decleare the struct or class in another header to not get "multiple declarations" exception when more than one file uses the class/struct.

The hard part is managing instantiation. To instantiate the object, C++ needs to know it's size. This means that in Oberon_Command's example, either the main.cpp file needs to be able to include a special header that gives the class definition, or you need to switch to dynamic allocation,e.g. the original header contains a factory function to return something like std::unique_ptr<Instance>.

That interface is very C like (opaque struct). Consider also the PIMPL idiom.


I didn't know that your could forward declear a class like that in the header and leave both declaration and definition of it's body to the source file.. thought that would cause the compiler to throw a multiple declarations error.. cool!

That's actually necessary in some cases. Consider the following examples of circular references:

[source]

struct Foo{

Bar* x; // error, Bar is not declared

};

struct Wololo;

struct Bar {

Wololo* x; // no error, Wololo was forward declared

};

struct Wololo {

Bar* x; // no error, we already declared Bar above

};

[/source]

Yeah, I know about foward declaration, but I thought you had to re-decleare the struct or class in another header to not get "multiple declarations" exception when more than one file uses the class/struct.

Don't forget that "headers" are not really a thing from the perspective of the compiler. When you #include something, the compiler is essentially copy-pasting that file into the current translation unit. Even if you put the class definition in a single header that gets included in multiple files, the compiler sees you as defining the same class multiple times anyway. The "multiple declarations" error only comes into play if the class definition occurs twice in the same translation unit or if you try to define a global twice in two different files (because the compiler needs to set aside some memory for the global and needs to know which translation unit the global came from, unlike class definitions which take up no memory).

Thanks again for your explanations!

I feel that I have learnt some great stuff from this thread!

I do have a follow up question however, that is a bit more implementation specific:

How come this works:

Test.h

namespace Test
{ 
    class Instance;
 
    Instance* CreateInstance();
}

Test.cpp

#include "Test.h"
 
namespace Test
{
    class Instance
    {
        public:
            Instance() {};
            ~Instance() {};
 
        private:
            int one = 1;
    };
 
    Instance* CreateInstance()
    {
        return new Instance();
    }
}

main.cpp

#include <iostream>
#include "Test.h"
 
int main()
{
    auto test = Test::CreateInstance();
 
    if (test != nullptr)
    {
        std::cout << "deleting" << std::endl;
 
        delete test;
    }
 
    return 0;
}

But this doesn't:

Test.h

#include <memory>
 
namespace Test
{ 
    class Instance;
 
    std::unique_ptr<Instance> CreateInstance();
}

Test.cpp

#include "Test.h"
 
namespace Test
{
    class Instance
    {
        public:
            Instance() {};
            ~Instance() {};
 
        private:
            int one = 1;
    };
 
    std::unique_ptr<Instance> CreateInstance()
    {
        return std::make_unique<Instance>();
    }
}

main.cpp

#include "Test.h"
 
int main()
{
    auto test = Test::CreateInstance();
 
    return 0;
}

With error "use of undefined type Test::Instance"?

From what I can gather by reading around is that you should be able to create a unique_ptr<T> as long as there is a constructor and destructor defined for T?

I am using the compiler that comes with VS for testing this, could it be compiler specific?

Thanks again! =)

Weird. I think this stackoverflow question discusses this. The response says that when ~Instance() is defined inline it 'might cause problems'. Try moving the destructor definition outside of class Instance.

C++: A Dialog | C++0x Features: Part1 (lambdas, auto, static_assert) , Part 2 (rvalue references) , Part 3 (decltype) | Write Games | Fix Your Timestep!

Consider what happens when your second test.h is included in another unit. The compiler has to generate the code for destroying T from the smart ptr, but it only has the forward declaration of T available.

The compiler cannot guarantee to do the right thing with the destructor then because it does not know, for example, if T derives from another class.

So it's not possible to use a smart ptr that calls destructor of T if T is only forward declared.

This topic is closed to new replies.

Advertisement