6

I've decided to get more acquainted with my favorite programming language, but only reading the standard is boring.

What are the most surprising, counter-intuitive, or just plain weird elements of C++? What has shocked you enough that you ran to your nearest compiler to check if it's really true?

I'll accept the first answer that I won't believe even after I've tested it. :)

György Andrasek
  • 8,187
  • 4
  • 48
  • 76

7 Answers7

11

I found it somewhat surprising that

class aclass
{
public:
int a;
};

some_function(aclass());

will have ainitialized to 0 in some_function, while

aclass ac;
some_function(ac);

will leave it unitinitalized. If you explicitly define the default constructor of aclass:

class aclass
{
public:
aclass(): a() {}
int a;
};

then aclass ac; will also initialize a to 0.

henle
  • 473
  • 1
  • 4
  • 16
  • what? care to explain why it differs? does it initialize stack space pushed for the sake of a call? – Matt Joiner Nov 01 '09 at 23:56
  • 7
    Using `aclass()` as the parameter makes the compiler fill out a default constructer that initializes everything to the default. But when you only declare a variable, no constructor is called unless you defined a default constructor yourself. – henle Nov 02 '09 at 01:22
  • hmm, it's true, i tried it out :P. +1 for something i didn't know. – Matt Joiner Nov 02 '09 at 05:01
10

Another answer I could add would be the throw() qualifier. For example:

void dosomething() throw()
{
   // ....
}

void doSomethingElse() throw(std::exception)
{
  // ....
}

Intuitively, this seems like a contract with the compiler, indicating that this function is not allowed to throw any exceptions except for those listed. But in reality, this does nothing at compile time. Rather it is a run-time mechanism and it will not prevent the function from actually throwing an exception. Even worse, if an unlisted exception is thrown, it terminates your application with a call to std::terminate().

Charles Salvia
  • 52,325
  • 13
  • 128
  • 140
  • 4
    Herb Sutter explains in great detail why you should never write exception specifications: http://www.gotw.ca/publications/mill22.htm – James McNellis Nov 01 '09 at 03:29
  • That's exactly why we use the throw statement commented out, just for documentation purpose... quite a pity :/ – Matthieu M. Nov 01 '09 at 14:00
8

The order of several declarators is actually unordered:

volatile long int const long extern unsigned x;

is the same as

extern const volatile unsigned long long int x;
Sam Harwell
  • 97,721
  • 20
  • 209
  • 280
  • 2
    Is that really so surprising? Care to explain why you were surprised? – akent Nov 01 '09 at 12:40
  • 10
    I, for one, didn't know you could separate the "long long int" part. – György Andrasek Nov 01 '09 at 13:22
  • 1
    Actually, it is illegal for storage specifiers like "extern" to appear inside the type specifier. They must appear before or after. Your first example is not valid C++, but it would be if you swapped "extern" and "unsigned". – Drew Dormann Nov 01 '09 at 16:51
  • @Shmoopty: based on the formal grammar rule alone, you are correct. However, section 7.1.6.2 (dcl.type.simple) point 3 states "When multiple *simple-type-specifiers* are allowed, they can be freely intermixed with other *decl-specifiers* in any order." This pulled from the C++0x draft here (not sure about the other standards): http://www.research.att.com/~bs/SC22-N-4411.pdf – Sam Harwell Nov 02 '09 at 12:47
6

Considering how unforgiving C++ usually is, I found it somewhat surprising that the standard actually allows you to delete null pointers.

Charles Salvia
  • 52,325
  • 13
  • 128
  • 140
  • 7
    I find it surprising how many people program in C++ that do not know that you can delete null pointers. – bk1e Nov 01 '09 at 17:31
  • I learned C before moving on to C++, so I learned not to do _anything_ with a NULL pointer. The fact that C++ defines a no-op for `delete NULL;` might be useful, but it can also hide bugs. – György Andrasek Nov 02 '09 at 02:51
  • 3
    No surprise there if you realize that in C `free(NULL)` is well-defined. – MSalters Nov 02 '09 at 10:35
2

Objects change type during construction.

In particular, when calling a virtual function from a constructor, you will not be calling the most derived override. Rather, you are calling the implementation from the class you're currently constructing. If that implementation happens to be 0 (pure virtual function), your program will crash at runtime.

A semi-real-world example:

class AbstractBase {
  public:
    AbstractBase() {
      log << "Creating " << className() << endl;
    }
  protected:
    virtual string className() const = 0;
}

class ConcreteGuy {
  protected:
    virtual string className() const { return "ConcreteGuy"; }
}

Upon constructing a ConcreteGuy object, the program will terminate with a "calling pure virtual function" error message.

This is why calling virtual functions from constructors is considered evil.

Caleb Huitt - cjhuitt
  • 14,785
  • 3
  • 42
  • 49
Thomas
  • 174,939
  • 50
  • 355
  • 478
  • 3
    its not that surprising really, when you construct a ConcreteGuy (assuming it is derived from AbstractBase) as the base class gets constructed first, it doesn't have a ConcreteGuy class to call until the constructor is complete. – gbjbaanb Nov 01 '09 at 14:28
  • 1
    It does make sense, I agree. But it still caused quite some headscratching when I first ran into this. Especially since Java and C# behave differently, allowing you to call methods on objects that are not fully constructed. – Thomas Nov 01 '09 at 14:52
  • 1
    @gbjbaanb (what a name!): Not really, the compiler will match the construction call with the most derived type constructors and start executing that constructor. Now, the order of execution is first the initialization list, then the constructor body, and the standard states that the initialization starts executing the first base class of the current class. The algorithm entails that the first type that is fully constructed (both initialization list and constructor body competed) is the less derived type (by some definition of it)... – David Rodríguez - dribeas Nov 01 '09 at 22:48
  • 2
    ... the fact is that the compiler knows the exact type of the most derived object beforehand. The fact that the virtual table gets initialized in the order it does is a design issue. Both Java and C# took a different approach, and while they will also construct from the base down to the most derived type, the virtual method table (or equivalent mechanism) is fully constructed before calling the base constructor with the most derived type methods. This, on the other hand is trading one problem for another, as a virtual method can be called on a not-yet-constructed level of the hierarchy. – David Rodríguez - dribeas Nov 01 '09 at 22:51
0

In C++ statements evaluate to something...

int main()
{
    "This is a valid C++ program!"
    "I will list the first five primes:";
    2;
    3;
    5;
    7;
    11;
}
Khaled Alshaya
  • 94,250
  • 39
  • 176
  • 234
-1

It is not very well known that an array initiator may jump in index like in the enumeration definition.

// initializing an array of int
int a[ 7] = { [5]=1, [2]=3, 2};
// resulting in
int a[ 7] = { 0, 0, 3, 2, 0, 1, 0};

// initializing an array of struct
struct { int x,y; } ar[ 4] = { [1].x=23, [3].y=34, [1].y=-1, [1].x=12};
// resulting in
struct { int x,y; } ar[ 4] = { { 0, 0}, { 12, -1}, { 0, 0}, { 0, 34}};

// interesting usage
char forbidden[ 256] = { ['a']=1, ['e']=1, ['i']=1, ['o']=1, ['u']=1};

Most of these are true for C++ also.

Lazer
  • 90,700
  • 113
  • 281
  • 364