2

I have a class in the file AType.h and it is implemented in AType.cpp.

# include "PrivateType.h"

class AType{
    private:
    int a, b, c;
    PrivateType varX;

    public:
    ...
};

I want to use the class AType in the file main.cpp and I will need to include AType.h, but I want to avoid the inclusion of PrivateType.h in main.cpp.
I cannot create varX with malloc/new.
main.cpp have to know the size of AType at compile-time.

Current solution: (It is bad)

1 - Create a program to print sizeof(AType).
2 - Change the header:

# ifdef ATYPE_CPP
    # include "PrivateType.h"
#endif

class AType{
    private:
    # ifdef ATYPE_CPP
        int a, b, c;
        PrivateType varX;
    # else
        char data[ the size that was printed ];
    # endif

    public:
    ...
};

3 - And AType.cpp will start with:

# define ATYPE_CPP
# include "AType.h"

Edit 1

Is there a way or tool to automatically change a complex struct into C primitive types?
I do not want to have to open header files and find the structs.

If PrivateType is:

struct DataType {
    float a, b;
};
class PrivateType {
    void* a;
    int b;
    short c;
    DataType x;

    ... functions
};

AType would be changed to:

class AType {
    int a, b, c;
    struct x {
        void* a;
        int b;
        short c;
        struct x2{
            float a, b;
        };
    };
};

And I would handle copy/equality methods separately.
I use GCC or Clang.

Edit 2
A new solution?

It is for GCC.

1 - Get sizeof(AType) and __alignof__(AType).
2 - Change the header:

# ifdef ATYPE_CPP
    # include "PrivateType.h"
#endif

class AType{
    private:
    # ifdef ATYPE_CPP
        int a, b, c;
        PrivateType varX;
    # else
        char data[ 'the sizeof(AType)' ];
    # endif

    public:
    ...
}
# ifdef ATYPE_CPP
    ;
# else
    __attribute__ (( aligned( 'The __alignof__(AType)' ) ));
# endif

3 - Write all copy/equality methods in AType.cpp.

Will it work?

Squall
  • 3,854
  • 6
  • 32
  • 45
  • 5
    Why do you not want to include the `PrivateType.h`? Can you just stick the types in a `Detail` namespace? – James McNellis Jan 16 '12 at 18:18
  • 3
    `#unclude `? `disusing namespace std;`? – Kerrek SB Jan 16 '12 at 18:19
  • What @JamesMcNellis is talking about, in C++ it's idiomatic to place "private" types inside a `namespace detail`, which indicates that they should only be accessed by the implementation code, not the user code. – Paul Manta Jan 16 '12 at 18:21
  • 1
    I think you're better off using the PIMPL idiom - http://stackoverflow.com/questions/60570/why-should-the-pimpl-idiom-be-used – eduffy Jan 16 '12 at 18:22
  • Is the constraint concerning `malloc`/`new` real? If you must have a proper member object, then you must include the header, end of story. Your char-array is not a good idea because you don't know that it'll be aligned properly. – Kerrek SB Jan 16 '12 at 18:27
  • you might get away with it but this breaks the one-definition rule – CashCow Jan 16 '12 at 18:41
  • It is just an example. I cannot let an internal C header be included. It is C and it has many symbols and macros. I am using third-party C libraries. – Squall Jan 16 '12 at 19:52
  • 2
    @H2CO3: Alignment is a fundamental property of types, and you can only safely store a variable at a memory location if the location is correctly aligned for the variable's type. An undecorated `char` array doesn't necessarily meet those requirements. – Kerrek SB Jan 16 '12 at 21:09
  • @KerrekSB I was not asking that (I know what alignment is!), I was referring to the #unclude preprocessor directive and the disusing keyword. –  Jan 16 '12 at 21:48
  • What do you have against using new though? – CashCow Jan 17 '12 at 09:32

6 Answers6

6

Your current method will fail subtly and catastrophically. The compiler must see the same declaration for the class at all times. Specifically, consider the compiler-generated equality or assignment operator for class AType without the proper definition of PrivateType present. The compiler would incorrectly generate a copy/equality method for a char array.

What you can do is forward declare your private type:

class PrivateType;

class AType{
    private:
    int a, b, c;
    PrivateType *varX;

    public:
    ...
};

Notice that varX is now a pointer to a class that hasn't been defined yet (of course you must allocate/deallocate this yourself; a smart pointer type might help). In your AType.cpp you can #include "PrivateType.h" to get the full definition so you can actually use the members of the class.

Greg Hewgill
  • 828,234
  • 170
  • 1,097
  • 1,237
  • This violates the stated constraints: "I cannot create varX with malloc/new." – James McNellis Jan 16 '12 at 18:22
  • Well, *somebody* has to allocate it. If the caller cannot know how to do that, then the class itself will have to take responsibility. – Greg Hewgill Jan 16 '12 at 18:24
  • 1
    @JamesMcNellis: Then use both: `varX=malloc(sizeof(*varX)); new(varX) PrivateType;`. Obviously, `AType::AType` can call `PrivateType::PrivateType`. – MSalters Jan 17 '12 at 10:10
5

You cannot do what you want (because you ruled out dynamic allocation), and your "solution" does not work in general, even if you avoid the problems with compiler-generated special member functions which others mentioned. One problem is that types not only have a size, but also an alignment. For example, your real class contains an int, however your replacement class contains only a char array. Now on most platforms, int has alignment 4 (i.e. an int must be located at a 4 byte boundary), while char has aligment 1 (it cannot have any other alignment without violating the standard). That is, as soon as you try to create an object with your replacement definition, you risk getting it misaligned, which in the best case will cause a massive slowdown, in the worst case a crash of your program (and in the absolutely worst case, it will work in your tests, but fail when actually used).

celtschk
  • 18,046
  • 2
  • 34
  • 61
1

The common solution is Pimpl: create a struct/class that holds the members of the public class. The old class has only one member: a pointer to this new struct/class. Observe:

struct ATypeData;

class AType
{
private:
    ATypeData *m_pData;

    public:
    ...
};

The source file for AType would have the actual definition of ATypeData, as well as the inclusion of any headers needed for those members.

Use of a smart pointer is suggested for the m_pData member, so that you don't have to have a destructor and copy constructor and all that good stuff.

Nicol Bolas
  • 378,677
  • 53
  • 635
  • 829
0

It is quite common for a class or a library to create a bunch of types that are not to be used anywhere else besides the lib itself. In particular, you do not want them the be created separately through malloc/new etc.

An ideal solution, which is not possible with the current C++ standards, would be to designate the type as private at the global scope. When C++20 introduces modules that may be a way.

What is typically done Today is to designate a special namespace (typically namespace detail) and put all your private types there. An unwritten rule is that the user of your header/lib is not allowed to use anything from the detail namespace directly. e.g. detail::PrivateType* foo = ... is forbidden.

At the same time this does not prevent you from accessing the detail::PrivateType objects that are already publically provided by the class/library - which is a good thing.

CygnusX1
  • 19,236
  • 3
  • 55
  • 100
0

There's no way to do what you want. Now such thing as "uninclude".
But, perhaps these ideas could be useful:

  1. Include PrivateType.h, then add preprocessor definitions which would prevent using it. Something like #define PrivateType dont use this!. If this is done after AType.h finished all its legitimate usage, these definitions should cause no trouble.

  2. Similarly to your solution, define an array instead of PrivateType. But:
    a. Define it as an array of void *. This will make sure the array is properly aligned (I assume no type has stricter alignment reqeirements than a pointer).
    b. Define the size as a simple constant. No running the program twice.
    c. In AType.c, verify that the size matches the real size. One way is to define two useless arrays, one of size DEFINED_SIZE-sizeof(AType), and the other of size sizeof(AType)-DEFINED_SIZE. IF there's a difference, compilation will fail because of an array with a negative size.
    d. You'll have to manually chang>e DEFINED_SIZE when changing AType. But you'll get a good reminder if you forget to.

ugoren
  • 15,127
  • 2
  • 28
  • 59
0

There is a way but I wouldn't suggest doing it. In AType.cpp you have some kind of compilation-unit scope map from each AType object to a PrivateType.

You could also have some kind of pool of PrivateType objects and then your AType would have a pointer (or even possibly a reference) to which one it has from the pool. Of course it restricts the number of AType objects you can have to the number in the pool.

Finally your option would be to use placement-new if you don't consider that also a banned use of new. In such a case you have your struct above with the memory in it, and also a PrivateType * member pointer. In your constructor you would use placement new to create your object in the memory space, in your destructor you would have to call the destructor of your private type. Your member could be a reference.

PrivateType & varX;

AType::AType : varX( *new(data)PrivateType )
{
}

~AType::Aype()
{
    varX.~PrivateType();
}

This sort-of models what you have now but uses the "evil" new word...

CashCow
  • 29,087
  • 4
  • 53
  • 86