简体   繁体   中英

Linker error with virtual functions c++

I am not sure what is wrong with this code, I am learning Builder pattern . The example code is in Java and I am trying to code the same in C++, butI am getting linker errors. I searched and read about it all and still couldn't find the right way and hence, posting it here. If I am missing something really trivial, my apologies.

#include <iostream>
#include <string>
#include <list>
#include <memory>
#include <conio.h>

using namespace std;
using std::string;
using std::unique_ptr;
using std::list;

class Packing
{
public:
    virtual string pack() = 0;
};

template<typename T>
class Item
{
public:
    virtual string name();
    virtual Packing* packing();
    virtual float price();
};

/* 
  As per comments, I have now defined the functions in my Item class, but
  the actual definition is in the derived classes and because of these 
  definitions, I am not getting the actual output. I have provided the 
  required and actual output of this code. 

  I also read about CRTP and have incorporated those changes as well. But 
  still am not able to figure out how to get Items in the list. 
*/
template<typename T>
string Item<T>::name()
{
    return "Item Class";
}

template<typename T>
Packing* Item<T>::packing()
{
    return (nullptr);
}

template<typename T>
float Item<T>::price()
{
    return 0.0f;
}

class Wrapper : public Packing
{
public:
    string pack() override
    {
        return "Wrapper";
    }
};

class Bottle : public Packing
{
public:
    string pack() override
    {
        return "Bottle";
    }
};

class Burger : public Item<Burger>
{
public:
    Packing* packing() override;
};

Packing* Burger::packing()
{
    return (new Wrapper());
}

class ColdDrink : public Item<ColdDrink>
{
public:
    Packing* packing() override;
};

Packing* ColdDrink::packing()
{
    return (new Bottle());
}

class VegBurger : public Burger
{
public:
    float price() override
    {
        return 25.0f;
    }

    string name() override
    {
        return "Veg Burger";
    }
};

class ChickenBurger : public Burger
{
public:
    float price() override
    {
        return 50.5f;
    }

    string name() override
    {
        return "Chicken Burger";
    }
};

class Coke : public Burger
{
public:
    float price() override
    {
        return 30.0f;
    }

    string name() override
    {
        return "Coke";
    }
};

class Pepsi : public Burger
{
public:
    float price() override
    {
        return 35.0f;
    }

    string name() override
    {
        return "Pepsi";
    }
};

class Meal
{
public:
    Meal() {}

    void addItem(Item& item)  // This is the error place after changing my 
                              // code to use templates. The error is:       
                              // 1>c:\users\xxx\documents\visual studio 
                  //2015\projects\mealbuilder\mealbuilder\mealbuilder.h(14):
                              // error C2955: 'Item': use of class template 
                              // requires template argument list

    {
        items.push_back(std::move(item));
    }

    float getCost()
    {
        float cost = 0.0f;
        for (auto& item : items)
        {
            cost += item.price();
        }

        return cost;
    }

    void showItems()
    {
        for (auto& item : items)
        {
            cout << "Item : " << item.name() << endl;
            cout << "Packing : " << item.packing() << endl;
            cout << "Price : " << item.price() << endl << endl;
        }
    }

private:
    list<Item> items;
};

class MealBuilder
{
public:
    Meal prepareVegMeal()
    {
        Meal meal;
        VegBurger vegBurger;
        Coke coke;
        meal.addItem(vegBurger);
        meal.addItem(coke);
        return meal;
    }

    Meal prepareNonVegMeal()
    {
        Meal meal;
        ChickenBurger chickenBurger;
        Pepsi pepsi;
        meal.addItem(chickenBurger);
        meal.addItem(pepsi);
        return meal;
    }
};

int main()
{
    MealBuilder mealBuilder;

    Meal vegMeal = mealBuilder.prepareVegMeal();
    cout << "Veg Meal: " << endl;
    vegMeal.showItems();
    cout << "Total cost: " << vegMeal.getCost();

    Meal nonVegMeal = mealBuilder.prepareNonVegMeal();
    cout << "Non-Veg Meal: " << endl;
    nonVegMeal.showItems();
    cout << "Total cost: " << nonVegMeal.getCost();

    _getch();
    return 0;
}

Following the comments, here is the error I used to get prior to adding the definition of the Item class:

1>------ Build started: Project: MealBuilder, Configuration: Debug Win32 ------
1>  MealBuilder.cpp
1>MealBuilder.obj : error LNK2001: unresolved external symbol "public: 
virtual class std::basic_string<char,struct std::char_traits<char>,class 
std::allocator<char> > __thiscall Item::name(void)" (?name@Item@@UAE?AV?
$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@XZ)
1>MealBuilder.obj : error LNK2001: unresolved external symbol "public: 
virtual class Packing * __thiscall Item::packing(void)" (?
packing@Item@@UAEPAVPacking@@XZ)
1>MealBuilder.obj : error LNK2001: unresolved external symbol "public: 
virtual float __thiscall Item::price(void)" (?price@Item@@UAEMXZ)
1>C:\Users\XXX\documents\visual studio 
2015\Projects\MealBuilder\Debug\MealBuilder.exe : fatal error LNK1120: 3 
unresolved externals
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

After adding the definitions, I get the following output:

Veg Meal:
Item : Item Class
Packing : 00000000
Price : 0

Item : Item Class
Packing : 00000000
Price : 0

Total cost: 0

Non-Veg Meal:
Item : Item Class
Packing : 00000000
Price : 0

Item : Item Class
Packing : 00000000
Price : 0

Total cost: 0

But the required output is:

Veg Meal
Item : Veg Burger, Packing : Wrapper, Price : 25.0
Item : Coke, Packing : Bottle, Price : 30.0
Total Cost: 55.0


Non-Veg Meal
Item : Chicken Burger, Packing : Wrapper, Price : 50.5
Item : Pepsi, Packing : Bottle, Price : 35.0
Total Cost: 85.5

I am not sure how to change my code to get the required output. Please help.

Thanks a lot, in advance.

The error is in these lines:

class MealBuilder
{
public:
    Meal prepareVegMeal()  <-- changed this line, removed reference
    {
        ....
    }

    Meal prepareNonVegMeal()  <-- changed this line, removed reference
    {
       ....
    }
};

Once you fix this, you will need member functions in Item class.

Reason: In C++, a non-const reference cannot bind to a temporary object. C++ doesn't want you to accidentally modify temporaries. You are returning using std::move. std::move is a cast to an rvalue reference. By convention, rvalue references are treated as "references you are permitted to move the data out of, as the caller promises they really don't need that data anymore".

Check this answer for more details.

How come a non-const reference cannot bind to a temporary object?

Your Item class member functions are declared but never defined. You might get away with it if you never get to instantiate this class or classes derived from it. However this should not be relied upon.

Virtual member function can be either a normal function or pure virtual.

class A {
  virtual void func1() {};
  virtual void func2() = 0;
}

Classes that have at least one pure virtual function can not be instantiated but one can use pointers and references to it.

Virtual function that declare must be defined.

class B {
  virtual func1();
}

int main() {
  new B;
}

This code would produce linkage error similar to ones you get.

Though you might not explicitly instantiate the class you implicitly instantiate it once instantiating a class derived from it:

class B {
  virtual func1();
}

class C : B {
  virtual func2() {}
}

int main() {
  new C;
}

This code would produce similar linking error.

Going back to your case. Make sure that all functions are either pure virtual or properly defined.

There's another error you have in your code. It is surprising that compiler lets it go through.

In functions

Meal& prepareVegMeal()
{
    Meal meal;
    //...
    return std::move(meal);
}

You are creating a temporary object and returning it by a reference. This temporary object is automatically destroyed upon scope end. Returning a pointer or a reference to a temporary object is a common mistake and leads to memory corruption. Most compilers can easily detect them. Looking at your attempt to use std::move here to silence the warning is wrong. std::move is just a way to tell programmer that this object is being moved from and left in unspecified state. What you really want to do here is to return the object by its value:

Meal prepareVegMeal() // Notice no reference
{
    Meal meal;
    //...
    return meal;
}

But be careful because virtual function calls do not work with value semantics.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM