gcc: Come usare __attribute ((__ may_alias__)), onde evitare di “derefencing tipo puntatore-punned” warning
-
26-10-2019 - |
Domanda
Ho avuto qualche codice che utilizza il tipo-gioco di parole per evitare di dover chiamare un membro di "oggetto" 's costruttore e distruttore a meno che / fino a quando è effettivamente necessario per utilizzare l'oggetto.
Funziona bene, ma sotto g ++ 4.4.3, ottengo questo avviso del compilatore temuto:
jaf@jeremy-desktop:~$ g++ -O3 -Wall puns.cpp
puns.cpp: In instantiation of ‘Lightweight<Heavyweight>’:
puns.cpp:68: instantiated from here
puns.cpp:12: warning: ignoring attributes applied to ‘Heavyweight’ after definition
puns.cpp: In destructor ‘Lightweight<T>::~Lightweight() [with T = Heavyweight]’:
puns.cpp:68: instantiated from here
puns.cpp:20: warning: dereferencing type-punned pointer will break strict-aliasing rules
puns.cpp: In member function ‘void Lightweight<T>::MethodThatGetsCalledRarely() [with T = Heavyweight]’:
puns.cpp:70: instantiated from here
puns.cpp:36: warning: dereferencing type-punned pointer will break strict-aliasing rules
I miei tentativi di codice a __attribute l'uso di gcc ((__ may_alias__)) per far gcc sapere circa il potenziale aliasing, ma gcc non sembra capire quello che sto cercando di raccontarla. Sto facendo qualcosa di sbagliato, o non gcc 4.4.3 basta avere alcuni problemi con l'attributo __may_alias__?
Codice giocattolo per riprodurre l'avviso del compilatore è qui sotto:
#include <stdio.h>
#include <memory> // for placement new
#include <stdlib.h> // for rand()
/** Templated class that I want to be quick to construct and destroy.
* In particular, I don't want to have T's constructor called unless
* I actually need it, and I also don't want to use dynamic allocation.
**/
template<class T> class Lightweight
{
private:
typedef T __attribute((__may_alias__)) T_may_alias;
public:
Lightweight() : _isObjectConstructed(false) {/* empty */}
~Lightweight()
{
// call object's destructor, only if we ever constructed it
if (_isObjectConstructed) (reinterpret_cast<T_may_alias *>(_optionalObject._buf))->~T_may_alias();
}
void MethodThatGetsCalledOften()
{
// Imagine some useful code here
}
void MethodThatGetsCalledRarely()
{
if (_isObjectConstructed == false)
{
// demand-construct the heavy object, since we actually need to use it now
(void) new (reinterpret_cast<T_may_alias *>(_optionalObject._buf)) T();
_isObjectConstructed = true;
}
(reinterpret_cast<T_may_alias *>(_optionalObject._buf))->DoSomething();
}
private:
union {
char _buf[sizeof(T)];
unsigned long long _thisIsOnlyHereToForceEightByteAlignment;
} _optionalObject;
bool _isObjectConstructed;
};
static int _iterationCounter = 0;
static int _heavyCounter = 0;
/** Example of a class that takes (relatively) a lot of resources to construct or destroy. */
class Heavyweight
{
public:
Heavyweight()
{
printf("Heavyweight constructor, this is an expensive call!\n");
_heavyCounter++;
}
void DoSomething() {/* Imagine some useful code here*/}
};
static void SomeMethod()
{
_iterationCounter++;
Lightweight<Heavyweight> obj;
if ((rand()%1000) != 0) obj.MethodThatGetsCalledOften();
else obj.MethodThatGetsCalledRarely();
}
int main(int argc, char ** argv)
{
for (int i=0; i<1000; i++) SomeMethod();
printf("Heavyweight ctor was executed only %i times out of %i iterations, we avoid %.1f%% of the ctor calls!.\n", _heavyCounter, _iterationCounter, 100.0f*(1.0f-(((float)_heavyCounter)/((float)_iterationCounter))));
return 0;
}
Soluzione
Penso che la typedef
confonde GCC. Questi tipi di attributi sembrano funzionare meglio se applicato direttamente alle definizioni di variabili.
Questa versione della classe funziona per me (GCC 4.6.0):
template<class T> class Lightweight
{
private:
// typedef T __attribute((__may_alias__)) T_may_alias;
public:
Lightweight() : _isObjectConstructed(false) {/* empty */}
~Lightweight()
{
// call object's destructor, only if we ever constructed it
if (_isObjectConstructed) {
T * __attribute__((__may_alias__)) p
= (reinterpret_cast<T *>(_optionalObject._buf));
p->~T();
}
}
void MethodThatGetsCalledOften()
{
// Imagine some useful code here
}
void MethodThatGetsCalledRarely()
{
T * __attribute__((__may_alias__)) p
= (reinterpret_cast<T *>(_optionalObject._buf));
if (_isObjectConstructed == false)
{
// demand-construct the heavy object, since we actually need to use it now
(void) new (p) T();
_isObjectConstructed = true;
}
p->DoSomething();
}
[etc.]
Altri suggerimenti
I sosterrebbe di avere la classe che contiene solo contenere un array di caratteri di dimensioni sufficienti a contenere il vostro "oggetto" membro e quindi utilizzando nuova collocazione per inizializzare in cima alla array di caratteri. Che ha il vantaggio di essere compatibile con le specifiche così come cross-compilatore. L'unico problema è che si deve conoscere la dimensione in caratteri del vostro oggetto membro, che può metterti nei guai.
C'è una ragione per cui non si può avere il membro sia un puntatore e utilizzare nuovi e cancellare?
Che cosa succede se si sostituisce _isObjectConstructed
con un puntatore all'oggetto:
class Lightweight { public: Lightweight() : object(NULL) {/* empty */} ~Lightweight() { // call object's destructor, only if we ever constructed it if (object) object->~T(); } void MethodThatGetsCalledOften() { // Imagine some useful code here } void MethodThatGetsCalledRarely() { if (!object) { // demand-construct the heavy object, since we actually need to use it now object = new (_optionalObject._buf) T(); } object->DoSomething(); } private: union { char _buf[sizeof(T)]; unsigned long long _thisIsOnlyHereToForceEightByteAlignment; } _optionalObject; T *object; };
Nota, senza estensione GCC, solo puro codice C ++.
Utilizzando un T*
invece di un bool
non sarà nemmeno fare Lightweight
ancor più grande!