Avoid unused variables get optimized out in C++?

130 views Asked by At

I am trying to use the side effect of constructing an unused variable so I don't want it to be optimized out.

OK, I know that using side effect in constructing a variable may not be good practice. So please allow me to explain the current situation.

I have an abstract class and a concrete class. I have implemented a factory that produce Abstract* pointers to concrete class. For any concrete class who wishes to be produced by the abstract class, they must be registered first. Here is a minimum (non-)working example:

// Abstract.hpp
#pragma once

class Abstract {
public:
    virtual void Hello() const = 0;
};
// Abstract.cc
#include "Abstract.hpp"
#include "Factory.hpp"

template<>
Factory<Abstract>* Factory<Abstract>::_the_factory = nullptr;
// Concrete.hpp
#include "Abstract.hpp"
#include "iostream"

class Concrete : public Abstract {
public:
    void Hello() const override {
        std::cerr << "B" << std::endl;
    }
};
// Concrete.cc
#include "Concrete.hpp"
#include "Factory.hpp"

const bool concrete_registered = Factory<Abstract>::GetInstance()->Register("concrete", []() {
    return new Concrete;
});
// Factory.hpp
#pragma once
#include <map>
#include <string>
#include <functional>
#include <iostream>

template <class Product>
class Factory {
public:
    using ProductCreator = std::function<Product*()>;

    static Factory* GetInstance() {
        if (!_the_factory) {
            _the_factory = new Factory;
        }
        return _the_factory;
    }

    bool Register(const std::string& name, const ProductCreator& creator) {
        bool result = _creator_map.insert(std::make_pair(name, creator)).second;
        if (result) {
            std::cerr << name << " registered" << std::endl;
        } else {
            std::cerr << name << " can't be registered" << std::endl;
        }
        return result;
    }

    Product* GetProduct(const std::string& name) {
        if (_creator_map.find(name) == _creator_map.end()) {
            std::cerr << "Unknown product name " << name << " detected";
        }
        return _creator_map[name]();
    }

private:
    static Factory* _the_factory;
    std::map<std::string, ProductCreator> _creator_map;
};

I was hoping, by making concrete_registered a constant, I can force the program to execute the registration code before main so that I can get the product of Concrete from the factory. Here is how I use them:

// main.cc
#include "Abstract.hpp"
#include "Factory.hpp"

int main() {
    auto ptr = Factory<Abstract>::GetInstance()->GetProduct("concrete");
    ptr->Hello();
}

The whole project has the following structure:

├── CMakeLists.txt
├── lib
│   ├── Abstract.cc
│   ├── Abstract.hpp
│   ├── CMakeLists.txt
│   ├── Concrete.cc
│   ├── Concrete.hpp
│   └── Factory.hpp
└── main.cc

And my CMakeLists.txt are:

# CMakeLists.txt
cmake_minimum_required(VERSION 3.22)
project(test)

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

add_subdirectory(lib)
add_executable(main main.cc)
target_link_libraries(main lib)
# lib/CMakeLists.txt
add_library(lib Abstract.cc Concrete.cc)
target_include_directories(lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

However, I got the following error when running the program:

terminate called after throwing an instance of 'std::bad_function_call'

And indeed I did not see the log indicating a successful registration of the Concrete objects (Note the logging in Factory.hpp)

After spending some time searching about this problem, I realize this may be the result of variable concrete_registered being unused so the linker does not link it to the main program. That is why I need to use the side effect of constructing for an unused variable.

My question: Is it possible to force C++ linker to link concrete_registered to the main program? I know some tricks like -Wl,--whole-archive -lXXX -Wl,--no-whole-archive but I am afraid linking the whole target may be too much, I just need this part of the code 'not optimized out'

1

There are 1 answers

0
mksteve On

Reading better, this is a side effect of how libraries work. When you have a library mylib.a, it has a number of object files

file1.o, file2.o, file3.o

These are pulled into the binary by the linker, when they are referenced. Your system creates concrete.o, with no exported data, so it won't get added by the linker to your binary. The linker can't see the abstract lookup from main.cc to the factory (implemented in main.cc), and figure that for the system to work it needs concrete.o to be added to the binary.

Try using objdump to see where implementations are.

You need to have a symbol from your concrete class to be pulled into you binary, or it will not work