Reputation: 308
So, I've got an issue, and I'm not sure if it's a language issue, or a compiler/GCC issue.
TL;DR - Am I required to define all static methods within a class, even if those static methods are never called by the application (i.e. could be legitimately dropped by the linker anyway)?
I have a library class that implements a device driver for a UART in a microcontroller. Because I don't want multiple UART objects pointing to the same resource, each UART object is a singleton, retrieved using one of several GetInstance()
methods, one for each UART instance in the device (UART0, UART1, etc). Each UART instance needs to have two FIFOs (Tx and Rx) for storage. Each FIFO needs to be explicitly sized by the application and allocated at the time that the UART object is instantiated (ideally). So I also have several static GetStorage()
methods, again, once for each UART.
I've created some stripped down code for my proof of concept. Here's static_instance.h:
#ifndef STATIC_INSTANCE_H_
#define STATIC_INSTANCE_H_
#ifdef __cplusplus
#include <vector>
namespace foo {
class Uart {
public:
/* Retrieve a singleton instance, using lazy static initialization. Note
* that not all instances will be present for a given device. */
static Uart& Uart1GetInstance(void);
static Uart& Uart2GetInstance(void);
static Uart& Uart3GetInstance(void);
/* Does something. */
void DoSomething(void) { ++counter; }
private:
/* Structure for the storage that each static Uart instance requires. */
struct Storage {
Storage(std::vector<char>& vector)
: my_vector_(vector) { }
std::vector<char>& my_vector_; // Buffer for data.
};
/* Instantiate object using provided register base and FIFO structures. */
Uart(int instance, Storage& storage)
: instance_(instance), storage_(storage) { }
~Uart() { }
/* Retrieves the storage required for the static Uart object instances.
* These methods are NOT implemented in static_instance.cc, but must be
* implemented in the application code, only for those Uart instances
* that are invoked in the application. */
static Storage& Uart1GetStorage(void);
static Storage& Uart2GetStorage(void);
static Storage& Uart3GetStorage(void);
int const instance_; // Instance number of this object.
Storage& storage_; // Allocated storage for this object.
int counter = 0; // Dummy counter.
};
} // namespace foo
#endif // __cplusplus
#endif
And here's static_instance.cc:
#include <static_instance.h>
namespace foo {
Uart& Uart::Uart1GetInstance(void) {
static Uart uart(1, Uart1GetStorage());
return uart;
}
Uart& Uart::Uart2GetInstance(void) {
static Uart uart(2, Uart2GetStorage());
return uart;
}
Uart& Uart::Uart3GetInstance(void) {
static Uart uart(3, Uart3GetStorage());
return uart;
}
} // namespace foo
The idea is that you only call GetInstance()
for the UART instance you actually need, and then only define GetStorage()
for that UART instance. (For this example, I'm only defining one buffer, and using std::vector<char>
as a stand-in.) Further, it's left to the application to define the storage method, because each application is going to have its own requirements for how big a given UART's buffer needs to be. (What I absolutely won't do is put macros in my C++ module, because, ew.) Here's the code snippet in main.cc to instantiate UART2:
namespace foo {
Uart::Storage& Uart::Uart2GetStorage(void) {
static std::vector<char> rx_vector(256, 0);
static Uart::Storage storage(rx_vector);
return storage;
}
static foo::Uart& uart_ = foo::Uart::Uart2GetInstance();
void wibble(void) {
uart_.DoSomething();
}
} // namespace foo
Now, I was developing an earlier application using an earlier IDE for this chip (Kinetis Design Studio v3.2.0 for the curious), which uses GCC 4.8.4 and compiles and links with no errors.
But NXP has deprecated KDS for another toolchain (MCUXpresso 10.0), which uses GCC 5.4.1, and using the exact same code, this time I get two linker errors:
./source/static_instance.o: In function 'foo::Uart::Uart1GetInstance()':
../source/static_instance.cc:5: undefined reference to 'foo::Uart::Uart1GetStorage()'
./source/static_instance.o: In function 'foo::Uart::Uart3GetInstance()':
../source/static_instance.cc:13: undefined reference to 'foo::Uart::Uart3GetStorage()'
I'm not sure why the linker cares that the GetStorage()
methods for UART1 and UART3 aren't defined, because I'm not calling GetInstance()
for either UART1 or UART3 in my application, and thus never calling the corresponding GetStorage()
methods either.
My question here is... does C++11 require me to have all three storage methods defined in my executable? That is, was GCC 4.8.4 letting me get away with something that I shouldn't have been able to? Or is this some GCC 5.4 option that I need to toggle, to allow me to drop unused static members from a class?
If the answer is "you have to define them regardless", then I'll define them, or maybe engineer some other way to allow. If the answer is "that should be OK", and there's no option that I can set on the command line to make GCC 5.4 do it, then I'll take the next step and report a bug in the NXP forums. Thanks.
Upvotes: 2
Views: 130
Reputation: 238311
TL;DR - Am I required to define all static methods within a class, even if those static methods are never called by the application
You may be required to define such static methods, even if they are never called by the application.
But, in general, you might not need to, if those functions are not odr-used (one definition rule). For static member functions, this is equivalent to
A function whose name appears as a potentially-evaluated expression
The difference is subtle. I hope this demonstrates it:
if(false)
function();
function
is never called, but appears in a potentially-evaluated expression, and is therefore odr-used and therefore must be defined.
My question here is... does C++11 require me to have all three storage methods defined in my executable?
Yes. They all appear in potentially-evaluated expressions and are therefore odr-used and therefore must be defined.
I was developing an earlier application using ... GCC 4.8.4 and compiles and links with no errors.
Odr violations have undefined behaviour, which explains why you didn't get an error / warning in the other tool chain.
Upvotes: 4
Reputation: 119847
[basic.def.odr]
- Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program outside of a discarded statement (9.4.1); no diagnostic required.
This is from a relatively new draft standard, but all versions contain a similar statement.
The "no diagnostic required" clause gives your compiler permission to accept your program even if it violates the rule. An odr-use within an unreachable piece of code is exactly the case when it is reasonable to do so: a compiler may or may not optimise away dead code that contains the offending call. Your program is still in violation and another implementation may reject it.
Upvotes: 2