2016-07-19 126 views
6

Sono sull'attività per migrare il concetto di gestione degli errori in una libreria di classi C++. I metodi che in precedenza hanno semplicemente restituito bool (success/fail) devono essere modificati per restituire un oggetto Result che trasmette un codice di errore leggibile dalla macchina e una spiegazione leggibile dall'uomo (e alcuni altri che non contano qui).C++: Posso rendere un operatore di assegnazione "esplicito"

Camminare attraverso migliaia di righe di codice è soggetto a errori, quindi cerco di ottenere il miglior supporto dal compilatore per questa attività.

La mia classe risultato ha - tra gli altri metodi membri - un costruttore che costruisce il risultato di un codice e un operatore di assegnazione per il codice:

class Result 
{ 
    public: 
     typedef unsigned long ResultCode; 
     explicit Result(ResultCode code); // (1) 
     Result& operator=(ResultCode code); // (2) 
}; 

Nota: Io di solito utilizzare una classe enum per ResultCode che risolverebbe i miei problemi, ma questa non è un'opzione. Questo perché l'obiettivo principale del progetto era utilizzare Result in diverse librerie, ognuna delle quali deve definire il proprio insieme di codici risultato senza richiedere un grosso file di intestazione che definisca tutti i possibili codici di risultato per tutte le librerie. In effetti, ogni classe deve essere in grado di definire i codici di risultato locali in modo che l'elenco dei possibili codici di risultato possa essere ottenuto dall'intestazione delle classi. Pertanto i codici non possono essere enumerati in Result, devono essere definiti dalle classi utilizzando la classe Result.

Per evitare conversioni implicite sulla

return true; 

Le dichiarazioni contenute nel codice client, il costruttore è stato dichiarato esplicitamente. Ma nelle chiamate al metodo di nidificazione, si verifica un altro problema. Dire, Ho un metodo

bool doSomething() 
{ 
    return true; 
} 

che sto usando in una funzione che restituisce un oggetto Result. Voglio trasmettere codici risultato di chiamate annidate

Result doSomethingElse 
{ 
    Result result = doSomething(); 
    return result; 
} 

Con l'attuale implementazione di operatore di assegnazione Result s', questo non sta a me dare un errore di compilazione - il valore di ritorno booleano di doSomething() viene implicitamente convertito lungo senza firma.

Come ho letto nella documentazione C++, solo i costruttori e gli operatori di conversione possono essere dichiarati espliciti.

Le mie domande

  1. Perché è esplicito non ammessi per gli operatori di assegnazione o altri metodi? IMO sarebbe molto sensato permettere che qualsiasi metodo fosse esplicito.
  2. Esistono altre soluzioni per impedire la conversione del tipo implicito per l'operatore di assegnazione?
+1

io so che non è la risposta che desidera, ma perché non utilizzare il meccanismo delle eccezioni in C++? Non combattere la lingua. Lavora con esso. (Più uno per la domanda ben posta però). – Bathsheba

+2

Non potresti dichiarare 'template void operator = (T) = delete;' e mantieni quello che hai. Lo userà normalmente e cercherà di usare il metodo cancellato per tutti gli altri tipi. – doug65536

+0

Non so se sia possibile o addirittura incoraggiato, ma potresti eventualmente estendere [le classi di errore di sistema] (http://en.cppreference.com/w/cpp/error#System_error) invece di inventarti tutto da solo ? –

risposta

1

With the current implementation of Result's assignment operator, this is not going to give me a compiler error - the boolean return value of doSomething() is implicitly converted to unsigned long.

Per quanto riguarda il codice che avete inviato; provoca un errore error: no viable conversion from 'bool' to 'Result', see here.

È necessario un esempio minimo che mostri il comportamento visualizzato nel codice. È probabile che altri costruttori o tipi con conversione nel codice effettivo abbiano un effetto materiale sul codice.


Sulle questioni esplicitamente chiesto ...

Why is explicit not allowed for assignment operators or other methods?

explicit è consentito solo quando la conversione implicita può avvenire, cioè dove il compilatore avrebbe tentato di generare la conversione per voi (v'è una speciale caso per bool). Tali conversioni sono costruttori e la conversione (o gli operatori di casting).

marcatura il costruttore o la conversione operatore explicit impedisce al compilatore di fare le conversioni, di conseguenza, se si richiede la conversione, è necessario essere espliciti su di esso - come una motivazione generale per il motivo per cui questo è fatto, rende il codice più esplicito in ciò che fa. C'è un trade-off, quindi l'uso giudizioso dovrebbe essere applicato in entrambi i casi. Il consiglio generale è di favorire explicit in caso di dubbio.

Ad esempio;

struct Result { 
    Result(long src); // can be marked explicit 
    operator long() const; // can be marked explicit 
}; 

Are there other solutions to prevent implicit type conversion for the assignment operator?

L'operatore di assegnazione ha una particolare Result& operator=(Result&);. Nell'assegnazione stessa, non ci sono conversioni. Per impedire la creazione implicita di un Result per il compito, i costruttori devono essere contrassegnati con explicit.

Per impedire che Result venga creato da un ResultCode, non è possibile dichiarare il metodo o contrassegnarlo come eliminato;

Result& operator=(ResultCode code) = delete; 
+0

Se non è la conversione, cosa sta succedendo dietro le quinte di un risultato risultato = true; quindi che mi lascia con un codice risultato di 1 –

+0

Che compilatore stai usando? Questo mi dà un errore in gcc, clang e msvc. – Niall

+0

@kritzel_sw. Hai un esempio minimale che mostra la compilazione (che non è come ti aspettavi)? – Niall

2

il problema non è nella classe Result: si sei creando esplicitamente una nuova istanza di esso, dopo tutto; explicit non può impedirlo.

Non penso che si possa proibire la promozione implicita bool -> long.

È possibile aggirare il problema. Un modo è rendere ResultCodenon essere un tipo intero. quindi, lo potrebbe avere un costruttore esplicito. Qualcosa di simile

class ResultCode 
{ 
unsigned long m_code; 
public: 
explicit ResultCode(unsigned long code) : m_code(code) {} 
operator unsigned long() { return m_code; } 
}; 

permetterebbe di utilizzare ResultCode ovunque è possibile utilizzare un unsigned int e creare come ResultCode res = 5 o return ResultCode(5) ma non chiamare una funzione in attesa di un ResultCode (come ad esempio il costruttore Result!) Con tutto ciò che non è un ResultCode già, né fare qualcosa come return 5 se la funzione deve restituire un ReturnCode.

In caso contrario è possibile utilizzare template overloadng a nulla 'cattura' non essendo un unsigned int e costringerlo a un errore

typedef unsigned long ResultCode; 

class Result 
{ 
    ResultCode m_par; 

public: 
    template<typename T> 
    Result(T param) { static_assert(false); } 

    template<> 
    Result(ResultCode par): m_par(par) {} 
}; 

int main() 
{ 
    ResultCode a = 5;  //ok 
    //unsigned long a = 6; //also ok 
    //bool a = true;  //error! 
    //int a = 7;   //also error!! 
    Result b(a); 
}