3

I was using an earlier version of Cocos2dx to write a game and compiling it with VS 2013. Note that I'm using CMake and Qt Creator with both compiler versions. When Cocos2dx v3.12 came out, I decided to upgrade the lib to that version in my game and started using VS 2015. Then I started getting this error:

QCardManager.cpp.obj:-1: error: LNK2001: unresolved external symbol "public: static class QCard * __cdecl QCard::create(enum PLAYER,struct Question const *,enum CARD_TYPE,int const &)" (?create@QCard@@SAPAV1@W4PLAYER@@PBUQuestion@@W4CARD_TYPE@@ABH@Z)

And I did not get that error when I was using VS 2013. After a couple of hours of debugging I found out the reason.

Here's the rough decleration of QCard:

#include "2d/CCSprite.h"
#include "CommonVariables.h"

class RandomPostureSprite;
class Question;
namespace cocos2d
{
class Label;
}

enum class CARD_TYPE {
    QUESTION,
    OPTION
};

class QCard : public cocos2d::Sprite
{
public:
    static QCard *create(PLAYER player, const Question *question, CARD_TYPE type, const int &index);
}

And I had the proper implementation of that function in QCard.cpp file and that file was also properly added to the project. So the problem was the class Question; forward declaration. I included the QuestionParser.h file in QCard.cpp but since I used a forward declaration for QCard in QCard.h, QCardManager.cpp file did not have the implementation for Question and hence the linker error.

Here's my question: I realize that what VS 2015 does should be the expected behaviour. But why is that behaviour happening? The same code compiles with no error on VS 2013 but not on VS 2015. I read the Breaking Changes in Visual C++ 2015 guide and couldn't not see anything that was related.

EDIT 1: Turns out the forward declaration should have been struct Question instead of class Question. When I try to use QCard::create in QCardManager.cpp I get the aforementioned linker error. But not in TimerHUD.cpp, which is in the same directory. I'll post the summary contents of them both. Keep in mind that I'm keeping the declaration of QCard the same with this edit.

The Question struct, which is in QuestionParser.h:

struct Question {
    Question()
        : type()
        , source()
        , alias()
        , color(0, 0, 0)
    {}
};

QCardManager.h

// Cocos2dx
#include "math/Vec2.h"
#include "math/CCGeometry.h"
// Utilities
#include "CommonVariables.h"
// Local
#include "GameDefinitions.h"
#include "QuestionParser.h"// This has the Question struct

// Forward declerations
class QCard;
namespace cocos2d
{
class Layer;
class Sprite;
}

class QCardManager
{
}

QCardManager.cpp

#include "QCardManager.h"
// Local
#include "QCard.h"
#include "RandomPostureSprite.h"
// Utilities
#include "GameManager.h"
#include "GameSettings.h"
#include "CocosUtils.h"
// Cocos2dx
#include "cocos2d.h"
using namespace cocos2d;

QCardManager::QCardManager(PLAYER player, Layer &parent)
{
    // This line gives the linker error
    QCard::create(PLAYER::PLAYER_ONE, nullptr, CARD_TYPE::QUESTION, 1);
}

QCardManager raises the linker error. But TimerHUD does not. I'm sharing the contents now.

TimerHUD.h

// Cocos2dx
#include "2d/CCNode.h"

namespace cocos2d
{
class Sprite;
class Label;
}

class TimerHUD : public cocos2d::Node
{
}

TimerHUD.cpp

// Cocos2dx
#include "cocos2d.h"
#include "SimpleAudioEngine.h"
// Local
#include "GameDefinitions.h"
// Utilities
#include "GameManager.h"
#include "GameSettings.h"
#include "CocosUtils.h"
#include "QCard.h"
using namespace cocos2d;

TimerHUD::TimerHUD()
{
    // This does not raise the linker error
    QCard::create(PLAYER::PLAYER_ONE, nullptr, CARD_TYPE::QUESTION, 1);
}
Furkanzmc
  • 104
  • 3
  • 13
  • You are right, `Question` is a `struct`. The problem goes away after changing the forward declaration from `class Question;` to `struct Question`. But that raises another question for me, before making that change the same code that raised error did not raise error in another class. I added more explanation in the edit to my question. Thanks. – Furkanzmc Aug 18 '16 at 09:31

1 Answers1

6

You shouldn't need Question's definition for this to link properly. The U in @PBUQuestion@ and the linker error seem to talk about struct Question instead of class Question, so you have a mismatch between the declaration and definition of Question. If you were to raise your warning level, you'd see that:

> type a.cpp
struct A;
class A {};

> cl /Wall a.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 19.00.24213.1 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

a.cpp
a.cpp(2): warning C4099: 'A': type name first seen using 'struct' now seen using 'class'
a.cpp(2): note: see declaration of 'A'

But because your warning level is too low, you don't get that diagnostic.

Your code seems to be valid from the standard's point of view. From C++11 9.1/2:

A declaration consisting solely of class-key identifier; is either a redeclaration of the name in the current scope or a forward declaration of the identifier as a class name. It introduces the class name into the current scope.

However, Visual C++ mangles the name of a function differently depending on whether a name is a class or a struct:

> undname ?f@@YAXPAUA@@@Z
void __cdecl f(struct A *)

> undname ?f@@YAXPAVA@@@Z
void __cdecl f(class A *)

Note that struct is mangled as U and class as V. This is definitely a bug in Visual C++, which shouldn't treat class and struct differently.

Once this is understood, your error is easy to reproduce:

> type a.cpp
class A;          // declares A as a class
void f(A* a);     // declares f(class A*)

int main()
{
    f(nullptr);   // calls f(class A*)
}


> type b.cpp
struct A {};      // defines A as a struct, mismatch!
void f(A* a) {}   // defines f(struct A*)!


> cl /nologo a.cpp b.cpp
a.cpp
b.cpp
Generating Code...
a.obj : error LNK2019: unresolved external symbol
    "void __cdecl f(class A *)" (?f@@YAXPAVA@@@Z) referenced
        in function _main
a.exe : fatal error LNK1120: 1 unresolved externals

So the reason you're having these weird issues is because the behaviour depends on whether a particular translation unit (basically, a .cpp) sees Question as a class or a struct. This in turn depends on which headers are included.

Note that you need the mismatch to be in different translation units. Within the same unit, Visual C++ will use the the class-key from the definition, even if there are different declarations before:

Compiler Warning (level 2) C4099

'identifier' : type name first seen using 'objecttype1' now seen using 'objecttype2'

An object declared as a structure is defined as a class, or an object declared as a class is defined as a structure. The compiler uses the type given in the definition.

As for the reason why this wasn't a problem in Visual C++ 2013, I doubt that the mangling scheme changed recently. It sounds like this bug has been present since at least 6.0. You may have changed the order of includes inadvertently.

Community
  • 1
  • 1
isanae
  • 3,253
  • 1
  • 22
  • 47