essere consapevoli che il seguente:
class Foo {
std::string m_bar = "Bar";
int m_baz = 3;
float m_boo = 4.2;
/* ... */
public:
Foo() {} // or Foo() = default or elision
};
int main() {
Foo f;
f.m_bar = "wunderBar";
}
espande per essere sulla falsariga di quanto segue:
Foo* fptr = stack_allocate<Foo*>(sizeof(Foo));
// from ctor
fptr->m_bar.string("Bar"); // construct m_bar
fptr->m_baz.int(3);
fptr->m_boo.float(4.2);
// your code:
fptr->m_bar.operator=("wunderBar");
Per ragioni simili, si potrebbe desiderare di guarda le istruzioni IL per il tuo costrutto C# - scoprirai che sta eseguendo operazioni ugualmente ridondanti (e in situazioni più complesse, possibilmente box/unboxing).
Il tuo approccio C++ ti mancherà anche quando incorporerai tipi non copiabili o non mobili che ti obbligheranno a passare puntatori e/o piegare il tuo progetto.
Cosa/sembra/si sta cercando di fare è ricreare parametri opzionali di Python:
# C++-alike
class Foo(object):
def __init__(self, Bar, Baz, Boo):
...
# C#-alike:
class Foo(object):
def __init__(self, Bar="Bar", Baz=13, Boo=4.2):
...
C++ non fornisce un modo diretto di fare questo, i meccanismi più vicini sono i parametri di default e overloading degli operatori:
class Foo {
std::string m_bar = "Bar";
int m_baz = 3;
float m_boo = 4.2;
public:
Foo(std::string bar="Bar", int baz=3, int boo=6.1)
: m_bar(bar), m_baz(baz), m_boo(boo)
{}
/* Foo* f = new Foo(); => new Foo(bar="Bar", baz=13, boo=6.1);
* Foo* f = new Foo("hello"); => new Foo(bar="hello", baz=3, boo=4.2);
* Foo* f = new Foo("hello", 1, 1.); => new Foo(bar="hello", baz=1, boo=1.);
* Foo* f = new Foo(42.); => invalid, arguments must be in order.
*/
};
o
class Foo {
std::string m_bar = "Bar";
int m_baz = 3;
float m_boo = 4.2;
public:
Foo() = default;
// allow Foo("hello")
Foo(const char* bar) : m_bar(bar) {}
Foo(const std::string& bar) : m_bar(bar) {}
Foo(std::string&& bar) : m_bar(std::forward(bar)) {}
// allow Foo(10, 12.)
explicit Foo(int baz, float boo) : m_baz(baz), m_boo(boo) {}
/* Foo* f = new Foo(); => new Foo(bar="Bar", baz=3, boo=4.2);
* Foo* f = new Foo("hello"); => new Foo(bar="hello", baz=3, boo=4.2);
* Foo* f = new Foo(1, 1.); => new Foo(bar="Bar", baz=1, boo=1.);
* Foo* f = new Foo(42.); => invalid, no match
*/
};
See http://ideone.com/yFIqlA per SSCE.
Se si dispone effettivamente di una dozzina di diverse configurazioni del costruttore, è probabile che si debba riconsiderare il progetto.
--- --- Modifica
Nota: Non è obbligatorio che si espone tutti i parametri nel costruttore:
class Foo {
std::string m_user_supplied;
std::time_t m_time;
public:
Foo() : m_user_supplied(), m_time(0) {}
Foo(std::string src) : m_user_supplied(src), m_time(0) {}
void addTime(time_t inc) { m_time += inc; }
};
--- --- Modifica 2
"dovrebbe forse ripensare il tuo design "... Un problema con elenchi di parametri grandi e opzionali è la crescita. È probabile che si finisca con parametri che dipendono l'uno dall'altro, che si contraddicono o interagiscono tra loro. È possibile scegliere di non convalidare questi o si può finire con costruttori complicati.
struct Foo {
...
FILE* m_output;
const char* m_mode;
...
Foo(..., FILE* output, const char* mode, ...)
{
...
if (output != nullptr) {
ASSERT(output == nullptr || mode != nullptr);
... other requirements
} else {
if (mode != nullptr)
... might not be an error but it might be a bug ...
}
...
}
};
Un approccio per evitare questo è l'utilizzo di incapsulamento/aggregazione di membri correlati.
class Foo {
...
struct FileAccess {
FILE* m_file;
const char* m_mode;
constexpr FileAccess() : m_file(nullptr), m_mode(nullptr) noexcept {}
FileAccess(FILE* file, const char* mode) : m_file(file), m_mode(mode) {
if (file == nullptr || mode == nullptr)
throw invalid_argument("file/mode cannot be null");
}
};
...
FileAccess m_access;
Foo(..., FileAccess access, ...);
};
Questo può essere un modo giusto per ridurre il rigonfiamento. Se la vostra API è stabile, è possibile utilizzarlo con le liste di inizializzazione (se la vostra API non è stabile e si modifica ti morderà nel culo)
auto fooWithFile = make_unique<Foo>{..., /*access=*/{stdout, "w"}, ...};
auto fooWithout = make_unique<Foo>{..., /*access=*/{}, ...};
Se successivamente si decide di smettere di usare ctors e passare a utilizzare setter, questo si tradurrà ragionevolmente bene, dato che è possibile avere sovraccaricato "set" che prende uno dei vari struct di configurazione:
auto foo = make_unique<Foo>();
foo->set(FileAccess(stdout, "w"))
->set(Position(Right, -100, Top, 180))
->set(DimensionPercentage(80, 75));
vs
auto foo = make_unique<Foo>() { # pseudo based on if C++ had the C# syntax
m_file = stdout;
m_mode = "w";
m_xPosition = -100;
m_xPositionRel = Right;
m_yPosition = -180;
m_yPositionRel = Top;
m_dimensionType = Percentage;
m_xDimension = 80;
m_yDimension = 75;
};
insieme con l'inizializzazione uniforme. Dovresti esaminare anche [std :: make_unique] (http://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique). 'new' è usato raramente nel moderno codice C++. –
È fantastico, ma non ho a disposizione C++ 14 per usarlo nella mia app attuale. Tuttavia, mi piacerebbe sapere se C++ 14 offre ciò che ho richiesto :) – Jimmyt1988
_ "Foo potrebbe assomigliare a questo" _ Questo è un modo piuttosto cattivo per guardare. Solo un costruttore predefinito? Come vengono inizializzati quei membri? A cosa? Come funziona l'utente della tua classe? –