Home > Enterprise >  Undefined reference while defining a class destructor in C
Undefined reference while defining a class destructor in C

Time:01-02

I am working on a small project to improve my OOP skills in C - the project is just a simple collection of books, sort of a home library, where the books can be stored in virtual shelves, grouped into bigger bookstands with respective names etc, with each book additionally having its own ID, being a positive integer. I was trying to create a user-defined class destructor and I wanted to move the destructor's implementation to a source file, following the rule to move longer than one-line-long implementations to a .cpp file from .hpp file to help building up a habit of producing a better optimized and faster code (of course in this example it will not help, but I just want to find a solution to the encountered problem, not to struggle in the future).

However, when trying to compile my code, I get the following error:

c:/mingw/bin/../lib/gcc/x86_64-w64-mingw32/9.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: CMakeFiles/bookshelf.dir/objects.a(bookshelf.cpp.obj): in function `void std::_Destroy<Book>(Book*)':
c:/mingw/include/c  /9.2.0/bits/stl_construct.h:98: undefined reference to `Book::~Book()'
collect2.exe: error: ld returned 1 exit status
make.exe[2]: *** [CMakeFiles/bookshelf.dir/build.make:101: bookshelf.exe] Error 1
make.exe[1]: *** [CMakeFiles/Makefile2:139: CMakeFiles/bookshelf.dir/all] Error 2
make.exe: *** [Makefile:111: all] Error 2
The terminal process "C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe -Command cmake --build Build" terminated with exit code: 1.

I use Visual Studio Code and compile my code with the MinGW64 compiler, working on Windows 11, and my project is set up with the following CMakeLists file:

cmake_minimum_required(VERSION 3.0.0)
project(Book VERSION 0.1.0)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)

# include(GoogleTest)
# enable_testing()

add_executable(main main.cpp)
add_executable(book src/book.cpp include/bookshelf.hpp include/book.hpp include/types.hpp include/helpers.hpp)
add_executable(bookshelf src/bookshelf.cpp include/bookshelf.hpp)
# add_executable(library src/library.cpp include/library.hpp)

add_compile_options(-Werror -Wextra -Wall -Wconversion -Wpedantic -pedantic-errors -unused-variable)

set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)

include_directories(
    include
    src
)

Here's the code, both for the header and the source file: book.cpp

#include <cstdlib>
#include <iostream>
#include <set>

#include "book.hpp"
#include "helpers.hpp"

Book::~Book(){
    ids.erase(book_id_);
    freed_ids.insert(book_id_);
    //@TODO: To be implemented - destroy the object, not only deal with the book's ID!
};

Book::Book(Title title, Author author) : title_(title), author_(author){
    if(!freed_ids.empty()){
    book_id_ = *(freed_ids.begin());
}
else if(freed_ids.empty() && !ids.empty()){
    book_id_ = *(ids.end());
}
else{
    book_id_ = 0;
}
ids.insert(book_id_);
};

int main(){
    std::cout << "Hello, from book.cpp!" << std::endl;
    return EXIT_SUCCESS;
}

book.hpp

#ifndef book
#define book

#include "types.hpp"
#include "helpers.hpp"

/*
Class represeting a book as en entry in a collection of books.
@param author_  Author of the book.
@param title_   Title of the book. 
@param book_id_ ID of a book. 
*/
class Book{
    private:
    Author author_;
    Title title_;
    BookID book_id_;

    public:
    Book(Title title, Author author);

    /*Get the title of the book.
    @TODO: To be moved into a new class Library*/
    Title get_title() const {return title_;}
    
    /*Get the author of the book.
    @TODO: To be moved into a new class Library*/
    Author get_author() const {return author_;}
    
    /*Get the ID number of the book.
    @TODO: To be moved into a new class Library*/
    BookID get_id() const {return book_id_;}

    ~Book();
};



#endif

Simply moving the destructor's body to the .hpp file makes the code compile just fine, but that is no solution to my problem. The constructor defined in the .cpp file works just fine, and I don't get it why the destructor does not work.

Thanks in advance for your help!

P.S. If something makes no sense in my code and is just stupid, feel free to tell me as well, intel like that would be very much appreciated.

@user17732522 I have applied the changes you suggested me in the answer below and the problem with the destructor does not occur now, but instead I get errors stating that there are multiple definitions of std::set objects I used to store IDs and names - they look like this:

c:/mingw/bin/../lib/gcc/x86_64-w64-mingw32/9.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe:
 CMakeFiles/main.dir/objects.a(bookshelf.cpp.obj):C:/Users/Darek/Desktop/projects/myLibrary/include/helpers.hpp:9: multiple definition of `ids';
 CMakeFiles/main.dir/objects.a(book.cpp.obj):C:/Users/Darek/Desktop/projects/myLibrary/include/helpers.hpp:9: first defined here

And two similar errors are present along with that one, except they refer to freed_ids and shelf_names std::set-type objects.

Here is the helpers.hpp file, not very complicated:

#ifndef HELPERS_HEADER_GUARD
#define HELPERS_HEADER_GUARD

#include <cstdlib>
#include <set>
#include <utility>
#include "types.hpp"

std::set<BookID> ids;
std::set<BookID> freed_ids;   
std::set<ShelfName> shelf_names;

#endif

And the bookshelf.cpp file also present in the error message:

#include <iostream>
#include <vector>
#include <exception>
#include <algorithm>

#include "bookshelf.hpp"

Bookshelf::Bookshelf(ShelfName shelf_name){
    if(std::find(shelf_names.begin(), shelf_names.end(), shelf_name) != shelf_names.end()){
        throw(std::invalid_argument("A bookshelf with the given name already exists."));
    }
    else{
        shelf_name_ = shelf_name;
        // content_ = {};
    }
}

CodePudding user response:

You are splitting your program into multiple:

add_executable(main main.cpp)
add_executable(book src/book.cpp include/bookshelf.hpp include/book.hpp include/types.hpp include/helpers.hpp)
add_executable(bookshelf src/bookshelf.cpp include/bookshelf.hpp)

You want only one executable. Furthermore, the header files do not belong there:

add_executable(main main.cpp src/book.cpp src/bookshelf.cpp)

(Btw. why is main.cpp not in src?)

On the other hand include_directories should include only the include directory:

include_directories(
    include
)

The reason for the undefined reference error is, that with your original configuration, CMake will create three independent programs. Consider only this one:

add_executable(bookshelf src/bookshelf.cpp include/bookshelf.hpp)

It will be build by compiling src/bookshelf.cpp, but not with src/book.cpp. (The .hpp files shouldn't matter here, but still don't belong in the command.).

So bookshelf.cpp probably contains some (member) variable of type Book. In order to use this variable, the program needs to know how to construct and destruct a variable of that type. In other words, it needs to have a definition of the constructor and destructor of Book. But the definition of both is only in the file book.cpp, which is not included in this program compilation.

  • Related