views:

357

answers:

6

Hi,

Lets say I have the following class:

class BritneySpears
{
  public:

    int getValue() { return m_value; };

  private:

    int m_value;
};

Which is an external library (that I can't change). I obviously can't change the value of m_value, only read it. Even subclassing BritneySpears won't work.

What if I define the following class:

class AshtonKutcher
{
  public:

    int getValue() { return m_value; };

  public:

    int m_value;
};

And then do:

BritneySpears b;

// Here comes the ugly hack
AshtonKutcher* a = reinterpret_cast<AshtonKutcher*>(&b);
a->m_value = 17;

// Print out the value
std::cout << b.getValue() << std::endl;

I know this is a bad practice.

But just for curiosity: is this guaranted to work ? Is it a defined behaviour ?

Bonus question: Have you ever had to use such an ugly hack ?

Thanks !

Edit: Just to scare less people: I don't intend to actually do this in real code. I just wonder ;)

+16  A: 

This is undefined behaviour. The members within each access-qualifier section are guaranteed to be laid out in the order they appear, but there is no such guarantee between acccess qualifiers. For instance, if the compiler chooses to place all private members before all public members, the above two classes will have a different layout.

Marcelo Cantos
So what If every member of `BritneySpears` was private, and every member of `AshtonKutcher` was public ? Would the order become guaranteed ?
ereOn
@ereOn: Only if they were all in a single access-qualifier section. Having two sections, both private, still results in an unspecified memory layout.
Marcelo Cantos
I still wonder how one can learn this without asking on SO. Thanks for the lesson ! :D
ereOn
The best strategy is to avoid clever tricks. ;-)
Marcelo Cantos
@ereOn For stuff like this, the only way to know for sure is to read and understand the standard.
KeithB
Even if the members were typographically identical in each class, you still could not guarantee the actual memory layout if they are compiled with different compiler options or different compilers.
Loadmaster
@KeithB I still don't master english good enough to be able to read such a standard without catching a serious headache.
ereOn
@Loadmaster: Good observation. My answer assumes the same compilation environment. Of course, one must assume this in order to get just about any program bigger than one compilation unit to work. The problem with mixing and matching access-qualifiers is that the layout can be different even in a single compilation unit, let alone across multiple compilation units with identical environments.
Marcelo Cantos
+1  A: 

Use of reinterpret_cast should usually be avoided and it's not guaranteed to give portable results.

Also, why do you want to change the private member? You could just wrap the original class in a new one (prefer composition over inheritance) and handle the getValue method as you wish.

kgiannakakis
Well, I was just wondering. I don't intend to this in any real code ;)
ereOn
Since the method `getValue` is not virtual, you will not be able to change its behavior in existing code by extending the class (all existing references at base level will call the base method, and the method --not really overriding-- in the derived class will never be called)
David Rodríguez - dribeas
That is correct. I removed it from the answer.
kgiannakakis
+7  A: 

This is undefined behavior, for the reasons Marcelo pointed out. But sometimes you need to resort to such things when integrating external code you can't modify. A simpler way to do it (and equally undefined behavior) is:

#define private public
#include "BritneySpears.h"
Kristo
This is a nice trick :)
ereOn
we can have private members without the label "private" so this may not work ;)
Nick D
@Nick D: Works on structs though (?)
Tom
@Nick: `#define class struct`. Not that I'd ever do anything like that.
Mike Seymour
This still doesn't solve the problem of object layout. If the library was built with a different compiler, then the layout of the `BritneySpears` class in the library is already set: `m_value` is in the `private` section, and there's no amount of messing with the header that will change that. So although you can trick your compiler into building your code with this hack, you still have the problem of incompatibility if your compiler and the compiler used to build the library do not lay out their `public` and `private` sections the same way.
Tyler McHenry
By the way, I know that you (Kristo) understand that this is undefined behavior, I just thought that this was worth pointing out explicitly.
Tyler McHenry
@Tyler, that's good info. Although, aren't you pretty well hosed in general if you mix compilers like that? C++ doesn't have a defined ABI after all.
Kristo
@Mike, oh yeah, `#define class struct`, `#define private public` is a lethal weapon!
Nick D
+2  A: 

@Marcelo has it right: the order of members is undefined across different access levels.

But consider the following code; here, AshtonKutcher has exactly the same layout as BritneySpears:

class AshtonKutcher
{
  public:
    int getValue() { return m_value; };
    friend void setValue(AshtonKutcher&, int);

  private:
    int m_value;
};

void setValue(AshtonKutcher& ac, int value) {
    ac.m_Value = value;
}

I believe that this may actually be valid C++.

Konrad Rudolph
Well, we have no guarantee that the compiler will order the different sections in the same order for two different classes. Well, I can't see which implementation would act like that, but still.
ereOn
+3  A: 

You might not be able to modify the library for BritneySpears, but you should be able to modify the .h header file. If so, you can make AshtonKutcher a friend of BritneySpears:

class BritneySpears 
{
    friend class AshtonKutcher;
  public: 

    int getValue() { return m_value; }; 

  private: 

    int m_value; 
}; 

class AshtonKutcher 
{ 
  public: 

    int getValue(const BritneySpears & ref) { return ref.m_value; }; 
}; 

I can't really condone this trick, and I don't think I've ever tried it myself, but it should be legal well-defined C++.

Mark Ransom
Who would want to be friends with Britney Spears?
FredOverflow
+1  A: 

There is an issue with your code, underlined by the answers. The problem comes from ordering the values.

However you were almost there:

class AshtonKutcher
{
public:

  int getValue() const { return m_value; }
  int& getValue() { return m_value; }

private:
  int m_value;
};

Now, you have the exact same layout because you have the same attributes, declared in the same order, and with the same access rights... and neither object has a virtual table.

The trick is thus not to change the access level, but to add a method :)

Unless, of course, I missed something.

Did I precise it was a maintenance nightmare ?

Matthieu M.