Undefined reference error when initializing unique_ptr with a static const
Asked Answered
M

3

9

When I try to use a static const to initialize a unique_ptr, I get an "undefined reference" error. However, when I new a pointer using the same constant, the symbol seems to be magically defined.

Here is a simple program that reproduces the error:

Outside_library.h

class Outside_library
{
public:
  static const int my_const = 100;
};

main.cpp

#include "Outside_library.h"

#include <iostream>
#include <memory>

class My_class
{
public:
  My_class(int num)
  {
    m_num = num;
  };

  virtual ~My_class(){};

private:
  int m_num;
};

int main(int, char* [])
{
  My_class* p_class = new My_class(Outside_library::my_const);
  delete p_class;

  // ==== ERROR HERE: When I comment this line out, the program runs fine.
  std::unique_ptr<My_class> p_unique
      = std::make_unique<My_class>(Outside_library::my_const);

  std::cout << "I made it through!" << std::endl;
  return 0;
}

I compiled the program using

g++ main.cpp -std=c++14

and got the following error.

/tmp/ccpJSQJS.o: In function `main':
main.cpp:(.text+0x51): undefined reference to `Outside_library::my_const'
collect2: error: ld returned 1 exit status

Can someone please help me understand why the constant is defined when using new, but not when using make_unique?

Magnetron answered 24/1, 2018 at 17:38 Comment(2)
@RickAstley: Decrease optimization level: DemoEdda
@Edda Good oneHokkaido
L
4

C++ has something known as the One-Definition Rule (ODR):

Informally, an object is odr-used if its value is read (unless it is a compile time constant) or written, its address is taken, or a reference is bound to it; a reference is odr-used if it is used and its referent is not known at compile time; and a function is odr-used if a function call to it is made or its address is taken. If an object, a reference or a function is odr-used, its definition must exist somewhere in the program; a violation of that is usually a link-time error.

The linked site gives the following example:

struct S {
    static const int x = 0; // static data member
    // a definition outside of class is required if it is odr-used
};
const int& f(const int& r);

int n = b ? (1, S::x) // S::x is not odr-used here
          : f(S::x);  // S::x is odr-used here: a definition is required

Your explicit constructor invocation does not "odr-use" Outside_library::my_const but the call to std::make_unique() does. When an object is odr-used it must have exactly one definition (not declaration). Your example has a declaration only. Again from cppreference:

  1. a variable x in a potentially-evaluated expression ex is odr-used unless both of the following are true:

    • applying lvalue-to-rvalue conversion to x yields a constant expression that doesn't invoke non-trivial functions
    • either x is not an object (that is, x is a reference) or, if x is an object, it is one of the potential results of a larger expression e, where that larger expression is either a discarded-value expression or has the lvalue-to-rvalue conversion applied to it

The solution as suggested by Jarod42 is to use constexpr instead of const (if you have control over the "outside library" code). If you do not, then you'll need to link the program against the library that contains the definition of Outside_library::my_const.

g++ main.cpp -std=c++14 -lOutside_library
Luau answered 24/1, 2018 at 17:51 Comment(4)
Wonder if constexpr is enough, clang still has linker issue DemoEdda
@Edda I didn't know constexpr's could be odr-used but I guess so: #26196595 and gcc just doesn't diagnose it. Interesting.Luau
And so, why does the new() call does not odr-use the static member? It looks like both the new() and std::make_unique are 'using' it?Avigdor
@Nuclear: It is actually up to the compiler to decide whether to odr-use it through a link-time reference or just calculate the constant value, since semantically the value is defined and can be evaluated in all contexts. This is not defined by the C++ standard. Turning on optimizations will also usually make the problem go away. Or passing in a variable which you initialize with the constant instead of passing the constant. And the same happens with new. The move semantics of the make_unique<> seems to trigger the odr-use for non-optimized executables.Brazier
E
3

make_unique takes (forwardind) reference of its parameter, and so odr-use it.

My_class(Outside_library::my_const) only use the value.

One solution is to define the member in a TU:

const int Outside_library::my_const;

or using constexpr value (since C++11):

class Outside_library
{
public:
    static constexpr int my_const = 100;
};

Edda answered 24/1, 2018 at 17:44 Comment(0)
B
0

If you cannot change the code which declares the static const int the following usually works:

std::make_unique<My_class>(Outside_library::my_const + 0);

Adding + 0 usually forces the compiler to evaluate this as an expression before it is passed through the move reference of make_unique<>. This avoids generating a (completely useless and unnecessary) link-time reference to the constant "variable".

Brazier answered 3/3, 2023 at 10:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.