-->

shielding #include within namespace { } block?

2020-08-09 07:00发布

问题:

Edit: I know that method 1 is essentially invalid and will probably use method 2, but I'm looking for the best hack or a better solution to mitigate rampant, mutable namespace proliferation.

I have multiple class or method definitions in one namespace that have different dependencies, and would like to use the fewest namespace blocks or explicit scopings possible but while grouping #include directives with the definitions that require them as best as possible. I've never seen any indication that any preprocessor could be told to exclude namespace {} scoping from #include contents, but I'm here to ask if something similar to this is possible: (see bottom for explanation of why I want something dead simple)

// NOTE: apple.h, etc., contents are *NOT* intended to be in namespace Foo!

// would prefer something most this:
#pragma magic_namespace_backout(1) // FIXME: use actually existing directive
namespace Foo {

#include "apple.h"
B *A::blah(B const *x) { /* ... */ }

#include "banana.h"
int B::whatever(C const &var) { /* ... */ }

#include "blueberry.h"
void B::something() { /* ... */ }

} // namespace Foo

...

// over this:
#include "apple.h"
#include "banana.h"
#include "blueberry.h"

namespace Foo {
B *A::blah(B const *x) { /* ... */ }
int B::whatever(C const &var) { /* ... */ }
void B::something() { /* ... */ }
} // namespace Foo

...

// or over this:
#include "apple.h"
namespace Foo {
B *A::blah(B const *x) { /* ... */ }
} // namespace Foo

#include "banana.h"
namespace Foo {
int B::whatever(C const &var) { /* ... */ }
} // namespace Foo

#include "blueberry.h"
namespace Foo {
void B::something() { /* ... */ }
} // namespace Foo

My real problem is that I have projects where a module may need to be branched but have coexisting components from the branches in the same program. I have classes like FooA, etc., that I've called Foo::A in the hopes being able to branch less painfully as Foo::v1_2::A, where some program may need both a Foo::A and a Foo::v1_2::A. I'd like "Foo" or "Foo::v1_2" to show up only really once per file, as a single namespace block, if possible. Moreover, I tend to prefer to locate blocks of #include directives immediately above the first definition in the file that requires them. What's my best choice, or alternatively, what should I be doing instead of hijacking the namespaces?

回答1:

Just think of #including as copying and pasting the contents of the included file to the position of the #include directive.

That means, yes, everything in the included file will be inside the namespace.



回答2:

Q: Can you:
A: Yes you can. The include statement is done during pre-processing before the compiler even sees it.

Q: Is it a good idea.
A: Probably not.

What happens if you #include Apple.g without the namespace tags.
now you have apples declared in the global namespace as-well as the foo namespace.

You should try and avoid situations where you the user of your code needs to understand how it should be used. If your documentation says always #include the apple header file inside the foo namespace that's the bit the user will not read and cause hours of confusion.



回答3:

Learn to love the third example, splitting it into three separate files as well. That'd really be the clearest way to go.

If you really want to include files inside of other namespaces, you could put } as the first character of the include file and namespace Whatever { at the end. But this would be awful.



回答4:

Did you edit this question?

The first block in your examples is not possible. You can't go up a namespace or whatever from within one like you want nor can you disable the namespace from within a file included in that namespace block. It simply can't be done that way.

Personally I prefer the first of your alternatives.

Edit, ok...here's something you could do (may need cleanup, untested):


#define MY_NAMESPACE Foo
#define NAMESPACE_WRAP(X) namespace MY_NAMESPACE { X }

#include "apple.h"
NAMESPACE_WRAP((B * A::blah(B const * x) {...}))

Pretty sure NAMESPACE_WRAP won't work for that kind of thing though so you'll probably need to put it in a different header or ".ipp" or whatever and do this:


#define NAMESPACE_WRAP(HEADER) \
namespace MY_NAMESPACE { \
#include HEADER \
}

Even that may not work and you'll have to go beyond my knowledge and look at how the boost preprocessor metaprogramming library does its include macros. You may actually find that that library ends up making what you want easier.

At any rate, it's not going to be as pretty as you want nor, IMHO, as readable and straight forward as the first alternative you presented.



回答5:

Probably, every module should refer to the "Foo::A" class, and you could place macro definition in the beginning of the module which needs other version of "A".

#include "apple.h"
#include "apple1_2.h"

//this module uses Version 1.2 of "Apple" class
#define Apple v1_2::Apple
namespace Foo {
B *A::blah(B const *x) 
{
    Foo::Apple apple; //apple is of type Foo::v1_2::Apple
    /* ... */ 
} 
int B::whatever(C const &var) { /* ... */ }
void B::something() { /* ... */ }
} // namespace Foo
#undef Apple

But this makes code harder to understand. Perhaps, if you need to choose between implementations of an object, you'll better use a factory function. That would make your intent explicit throughout the code.

AppleBaseClass* createApple(int version)
{
    if(version == 0)
        return new Foo::Apple;
    else if(version == 1)
        return new Foo::v1_2::Apple;
}
//usage
AppleBaseClass* apple = createApple(apple_version);

//compile-time equivalent
//metafunction CreateApple
template<int version> struct CreateApple {};
template<>
struct CreateApple<0> 
{
    typedef Foo::Apple ret;
};
template<>
struct CreateApple<1> 
{
    typedef Foo::v1_2::Apple ret;
};
//usage
CreateApple<apple_version>::ret apple;


回答6:

Method 2 all the way.
i always work with these simple rules:

1.) Source code should be CLEAR, EASY-TO-UNDERSTAND and DUMMY-PROOF.
-A good product is not built by single person. Simple, intuitive and easy-to-follow formatting will make everyones life happier.

2.) If there will be no performance difference in final product, stick to rule #1
-It makes no sense for developer to spend braincells on something that doesn't benefit end-customer.

3.) Elegant design will always works fine, so rule #2 always hold true.
-Same rule applies to God, take a mirror and look on yourself :)