I'm coding a simple c++ factory and I get an error that I don't understand.
This is my class definition:
MSGFactory.hpp
class MSGFactory
{
public:
static MSGFactory * getInstance();
void registerMSG(const std::string MSGType , createMSGFn constructor );
MSGData *createMessage(const std::string &MSGType);
private:
static MSGFactory * inst;
std::map<std::string,createMSGFn> MSGPool;
};
and this is its implementation:
MSGFactory.cpp
MSGFactory * MSGFactory::getInstance()
{
if(inst == NULL) inst = new MSGFactory();
return inst;
}
void MSGFactory::registerMSG(const std::string MSGType , createMSGFn constructor )
{
MSGPool.insert(factoryBucket(MSGType,constructor));
}
MSGData * MSGFactory::createMessage(const std::string &MSGType)
{
std::map<std::string,createMSGFn>::iterator it;
it = MSGPool.find(MSGType);
if( it != MSGPool.end() )
return it->second();
return NULL;
}
I wrote this test program:
T_MSGFactory.cpp
class MSGOne : MSGData
{
public:
MSGOne(){}
~MSGOne() {{std::cout<<"Derived destructor called\n";}}
std::string type() { std::cout << "Type One" << std::endl; return " ";}
unsigned char* getData(){ return (unsigned char *) "a"; }
static MSGData * Create() { return new MSGOne(); }
};
class MSGTwo : MSGData
{
public:
MSGTwo(){}
~MSGTwo() {std::cout<<"Derived destructor called\n";}
std::string type() { std::cout << "Type Two" << std::endl; return " ";}
unsigned char* getData(){ return (unsigned char *) "a"; }
static MSGData * Create() { return new MSGTwo(); }
};
class MSGThree : MSGData
{
public:
MSGThree(){}
~MSGThree() {std::cout<<"Derived destructor called\n";}
std::string type() { std::cout << "Type Three" << std::endl; return " ";}
unsigned char* getData(){ return (unsigned char *) "a"; }
static MSGData * Create() { return new MSGThree(); }
};
int main(int argc, char **argv)
{
std::cout << "PROVA" << std::endl;
MSGFactory::getInstance()->registerMSG("ONE", &MSGOne::Create );
MSGFactory::getInstance()->registerMSG("TWO", &MSGTwo::Create );
MSGFactory::getInstance()->registerMSG("THREE", &MSGThree::Create );
return 0;
}
but compiling it with "g++ T_MSGFactory.cpp MSGFactory.cpp" I get this error:
1) "riferimento non definito a" is "undefined reference to"
2) "nella funzione" is "in function"
Can someone help me? Thanks
In the top of your MSGFactory.cpp file, just after the includes, in the global scope, you should declare the static member.
like this:
MSGFactory* MSGFactory::inst=NULL;
Static member variables must be defined, usually in the source file.
Add this to your MSGFactory.cpp
MSGFactory* MSGFactory::inst = 0;
http://www.learncpp.com/cpp-tutorial/811-static-member-variables/
Related
I have an abstract class "Mark" and it has a child class "Int_num". I also have a "Subject" class. I want a pointer to the address in the memory of the "Mark" class to be written to the "mark" parameter when calling its constructor. What should I do to make the mark pointer point to the "Mark" class?" occurred, after the compiler complaint about "expression must have class type" or something like that in mark.print_mark()?
class Mark {
private:
int mark;
public:
virtual void change_mark(int);
virtual void print_mark();
virtual int return_mark();
};
class Int_mark : public Mark {
private:
int mark;
public:
Int_mark();
Int_mark(int);
~Int_mark();
void change_mark(int = 0);
void print_mark() const;
int return_mark() const;
};
Int_mark::Int_mark() {
std::string str_mark;
std::cout << "New mark: ";
std::cin.ignore();
std::getline(std::cin, str_mark);
str_mark = ltrim(rtrim(str_mark));
int new_mark;
try {
new_mark = stoi(str_mark);
} catch(...) {
std::cout <<"wq";
mark = 1;
return ;
}
try {
if((new_mark < 1) || (new_mark > 5))
throw 1;
else
mark = new_mark;
} catch(int a) {
std::cout << "qw" << std::endl;
mark = 1;
}
}
void Int_mark::print_mark() const {
std::cout << "Mark: " << mark << std::endl;
}
Subject
#include "Mark.h"
#include <string>
#include <vector>
class Subject {
private:
std::string name_subject;
std::string type_subject;
unsigned hour_subject = 0;
void *mark = nullptr;
public:
Subject();
Subject(std::string, int);
Subject(std::string, bool);
~Subject();
void change_mark(unsigned);
void change_mark(bool);
void rename_subj(std::string);
void add_hour(unsigned);
};
Subject::Subject() {
std::string name_sub;
std::cout << "Введите название предмета: ";
getline(std::cin, name_sub);
name_sub = split_string(name_sub);
name_subject = name_sub;
int select = 2;
if(select == 1) {
type_subject = "Bool";
//mark = new Bool_mark();
} else {
type_subject = "Int";
mark = new Int_mark();
//What should I do to make the mark pointer point to the "Mark" class?
mark.print_mark();
}
}
main
#include "subject/Subject.h"
using namespace std;
int main() {
Subject q;
}
What am I doing wrong? How should I do this?
The pointer mark is of type void *. You could cast it with
static_cast<Int_mark*>(mark)
and call the function with
static_cast<Int_mark*>(mark)->print_mark();
But usually in OOP mark would be a pointer to the base class
Mark *mark = nullptr;
Now you can check for errors with
mark = new Int_mark();
auto *m = dynamic_cast<Int_mark*>(mark);
if (m)
m->print_mark();
Remember the virtual destructor in the base class
virtual ~Mark();
When to use virtual destructors?
Here is a fixed version of your code:
#include <iostream>
#include <string>
#include <vector>
class Mark {
public:
virtual ~Mark() = default;
//virtual void change_mark(int) = 0;
virtual void print_mark() const = 0;
//virtual int return_mark() const = 0;
};
class Int_mark : public Mark {
private:
int mark;
public:
Int_mark();
Int_mark(int);
~Int_mark() override = default;
//void change_mark(int = 0) override;
void print_mark() const override;
//int return_mark() const override;
};
Int_mark::Int_mark() {
std::string str_mark;
std::cout << "New mark: ";
std::cin.ignore();
std::getline(std::cin, str_mark);
//str_mark = ltrim(rtrim(str_mark));
int new_mark;
try {
new_mark = stoi(str_mark);
} catch(...) {
std::cout <<"wq";
mark = 1;
return ;
}
try {
if((new_mark < 1) || (new_mark > 5))
throw 1;
else
mark = new_mark;
} catch(int a) {
std::cout << "qw" << std::endl;
mark = 1;
}
}
void Int_mark::print_mark() const {
std::cout << "Mark: " << mark << std::endl;
}
class Subject {
private:
std::string name_subject;
std::string type_subject;
unsigned hour_subject = 0;
Mark *mark = nullptr;
public:
Subject();
Subject(std::string, int);
Subject(std::string, bool);
~Subject();
void change_mark(unsigned);
void change_mark(bool);
void rename_subj(std::string);
void add_hour(unsigned);
};
Subject::Subject() {
std::string name_sub;
std::cout << "Введите название предмета: ";
getline(std::cin, name_sub);
//name_sub = split_string(name_sub);
name_subject = name_sub;
int select = 2;
if(select == 1) {
type_subject = "Bool";
//mark = new Bool_mark();
} else {
type_subject = "Int";
mark = new Int_mark();
auto *m = dynamic_cast<Int_mark*>(mark);
if (m)
m->print_mark();
}
}
Subject::~Subject() {
delete mark;
}
int main() {
Subject q;
}
Since I did not correctly understand the question in the first place, here a way how you can call the member function of base class Mark by object of derived class Int_Mark:
Int_mark *mark = new Int_mark();
mark->print_mark(); // calls member of the class Int_mark
mark->Mark::print_mark(); // calls member of the class Mark
Make sure that Mark::print_mark() is also defined and not just Int_mark::print_mark()
I've got a class that needs to become a singleton. All good and fine only that it has a non-default constructor, i.e. it takes three arguments.
The best I could come up with is, to set the constuctor private and then provide some kind of public "setup" function.
Are there any better solutions? My work around so far looks something like - any ideas to improve this are welcome!
#include <iostream>
class singltn {
private:
static singltn *instance;
int data;
// Private constructor so that no objects can be created.
singltn() {
data = 0;
}
int _AA;
int _BB;
int _CC;
public:
static singltn *getInstance() {
if (!instance)
instance = new singltn;
return instance;
}
void setup(int AA, int BB, int CC) {
_AA = AA;
_BB = BB;
_CC = CC;
}
int getData() {
return this->data;
}
void setData(int data) {
this -> data = data;
}
int getAA(){
return this->_AA;
}
};
//Initialize pointer to zero so that it can be initialized in first call to getInstance
singltn *singltn::instance = 0;
int main(){
singltn *a = a->getInstance();
a->setup(111,222,333);
std::cout << "dat " << a->getData() << " _AA " << a-> getAA() << std::endl;
a->setData(100);
std::cout << "dat " << a->getData() << " _AA " << a-> getAA() << std::endl;
singltn *b = b->getInstance();
std::cout << "dat " << b->getData() << " _AA " << a-> getAA() << std::endl;
return 0;
}
You can use a static factory/getter function that calls a private constructor. Something like this:
class Foo {
public:
static Foo& GetInstance() {
static Foo foo(param1, param2);
return foo;
}
private:
Foo(int a, int b) {
// ...
}
}
Of course, that requires your factory function to somehow know the parameters somehow.
Don't use real singleton (which handles creation + unique instance + global access), and adapt it:
class MyClass
{
private:
int data = 0;
int _AA;
int _BB;
int _CC;
static std::unique_ptr<MyClass> uniqueInstance;
MyClass(int AA, int BB, int CC) : _AA(AA), _BB(BB), _CC(CC) {}
MyClass(const MyClass&) = delete;
MyClass& operator =(const MyClass&) = delete;
public:
static void Create(int AA, int BB, int CC)
{
// if (uniqueInstance) throw std::runtime_error("Call it only once");
uniqueInstance = std::make_unique<MyClass>(AA, BB, CC);
}
static MyClass& GetInstance()
{
if (!uniqueInstance) throw std::runtime_error("Call Create before");
return *uniqueInstance;
}
int getData() const { return this->data; }
void setData(int data) { this->data = data; }
int getAA() const { return _AA; }
};
std::unique_ptr<MyClass> MyClass::uniqueInstance;
int main(){
MyClass::Create(111, 222, 333);
auto& a = MyClass::GetInstance();
std::cout << "dat " << a.getData() << " _AA " << a.getAA() << std::endl;
a.setData(100);
std::cout << "dat " << a.getData() << " _AA " << a.getAA() << std::endl;
}
Adapting Steve's Answer to provide a setup function for the singleton class:
class Foo {
public:
static Foo& GetInstance() { return SetupInstance(-1, -1); }
static Foo& SetupInstance(int a, int b) {
static Foo foo(a, b);
return foo;
}
private:
Foo(int a, int b) {
// ...
}
};
As the constructor of the static singleton get's only called once, multiple calls to SetupInstance will not re-create a new object of Foo, but always return the object which was created at the first call.
In the code bellow, instead of using new function "void print()", how can I use the overloaded "<<" operator in order to print the required information?
Or to be exact, where is the mistake here?
Overloaded << operator in one of the inherited classes:
friend ostream &operator<<(ostream &os, DigitSecret &s){
for(int i=0;i<s.n;i++)
os<<s.digits[i];
return os<<" Simple entropy: "<<s.simpleEntropy()<<" Total: "<<s.total();
}
void printAll (Secret ** secrets, int n) {
for(int i=0;i<n;i++){
cout<<secret[i] //This is printing an address, however that is not what i want.
secrets[i]->print(); //I want that to work like this.
}
}
The whole code: https://pastebin.com/MDCsqUxJ
I want line 134 and 143 to work correctly.
EDIT:
secret[i] is of type Secret*, you should derefence first and then your overload will get picked:
cout << *secret[i];
Side note: use std::vector instead of raw dynamic allocation.
See this snippet:
class base {
public:
virtual void print() = 0;
virtual std::ostringstream get_value() const = 0;
int get_id() const { return id_; }
protected:
int id_;
};
class A:public base {
public:
A(std::string val):val_(val){ id_ = 1; }
void print() override { std::cout << " I am A" << std::endl; }
std::ostringstream get_value() const { std::ostringstream ss; ss << val_; return ss; }
private:
std::string val_;
};
class B :public base {
public:
B(int val):val_(val) { id_ = 2; }
void print() override { std::cout << " I am B" << std::endl; }
virtual std::ostringstream get_value() const { std::ostringstream ss; ss << val_; return ss; }
private:
int val_;
};
std::ostream& operator << (std::ostream& os, const base* p)
{
std::string str;
if (p->get_id() == 1) {
str = ((A*)(p))->get_value().str();
os << "A " << str << "\n";
}
else
if (p->get_id() == 2) {
str = ((B*)(p))->get_value().str();
os << "B " << str << "\n";
}
return os;
}
void PrintAll(base** a)
{
for (int i = 0; i<2; i++)
std::cout << a[i];
}
int main()
{
base* a[2];
a[0] = new A("Hello");
a[1] = new B(10);
PrintAll(a);
return 0;
}
Output:
I Solved it this way:
void printAll (Secret ** secrets, int n) {
for(int i=0;i<n;i++){
DigitSecret* ds = NULL;
CharSecret* cs = NULL;
ds = dynamic_cast<DigitSecret*>(secrets[i]);
cs = dynamic_cast<CharSecret*>(secrets[i]);
if(ds!=NULL)
cout<<*ds<<endl;
else
cout<<*cs<<endl;
// secrets[i]->print();
}
}
Basically in this case, I have to use dynamic_cast with new pointer from the derived class, on each pointer from the array, and check if the pointer is !=NULL, and then use the overloaded operator on the dereferenced new pointer.
I have this C++ class:
class Test
{
private:
string _string;
public:
Test()
{
}
Test(const char *s)
{
Test((string)s);
}
Test(string s)
{
_string = s;
}
operator const char *()
{
return _string.c_str();
}
operator string()
{
return _string;
}
};
If I use this code in main "1234" is printed to the console:
int main()
{
Test test = string("1234");
string s = test;
cout << s << endl;
return 0;
}
But with this, nothing is printed:
int main()
{
Test test = "1234"; // Only change
string s = test;
cout << s << endl;
return 0;
}
The only difference is which constructor is called. It apperas that the _string variable is a default string instance with the value "" but I don't see how that could have happend. I thought that since _string is on the stack, the assignment I do is safe.
This
Test(const char *s)
{
Test((string)s);
}
does not chain the constructors. It just creates a temporary object in the body of the function. What you need is:
Test(const char *s) : Test(string(s))
{
}
Suppose I have two classes a base class and an inherited class as follows:
class Magic : public Attack
{
public:
Magic(int uID, std::string &name) : Attack(ActionType::MagicAction, uID, name)
{
}
};
class MacroMagic : public Magic
{
MacroMagic(int uID) : Magic(uID, std::string("Testing"))
{
}
void PreUse() override
{
std::cout << "Test" << std::endl;
}
}
I have a shared_ptr to an instance of magic that I would like to copy, but at runtime I will not know whether that pointer points to an instance of Magic, MacroMagic or anything that may have inherited from Magic. I want to be able to copy the object pointed to by the shared_ptr like so:
Battles::magic_ptr mgPtr = MagicNameMap[name];
if (mgPtr.get() != nullptr)
{
return magic_ptr(new Magic(*mgPtr));
}
return mgPtr;
where magic_ptr is a typedef for a shared_ptr around the Magic Class. I could do it by specifying a virtual copy function and calling that, but I'd like to make it less obtuse and easier to maintain. I assume I can do this by a copy constructor, but I'm unsure how to in this instance. The way I have it now, the pointer returned by the above code will not call the override pReUse() function.
A bit of guidance would be much appreciated, thanks
I could do it by specifying a virtual copy function and calling that, but I'd like to make it less obtuse and easier to maintain.
you're working against the language.
you could accomplish what you are after, but i don't recommend it, and it is certainly not easier to setup or maintain than virtual Magic* clone() = 0.
perhaps you could outline the problem that brought you to this conclusion, and then we can help from there. there are usually alternatives which don't fight the language.
EDIT
here's a way around it using an external function table (t_magic_operation_table). you can apply and create several function tables and keep them around. since they exist in the magic object as a single pointer, then you can make these tables quite large (if needed). if your magic types can use the same data/members, then that is one approach. be careful: i threw this together suuuper-fast. it demonstrates the technique, but it's pretty bad otherwise:
#include <iostream>
#include <string>
namespace MONSpiel {
inline unsigned prndm(const unsigned& max) {
return 1 + arc4random() % max;
}
class t_ghoul;
class t_biclops;
class t_magic;
class t_hero {
t_hero();
t_hero(const t_hero&);
t_hero& operator=(const t_hero&);
public:
t_hero(const std::string& inName) : d_name(inName) {
}
const std::string& name() const {
return this->d_name;
}
template<typename TEnemy, typename TMagic>
void attack(TEnemy& enemy, TMagic& magic) const {
if (enemy.isDead()) {
return;
}
enemy.hit(magic.power());
if (enemy.isDead()) {
std::cout << this->name() << ": see you in the prequel...\n\n";
}
else {
std::cout << this->name() << ": have you had enough " << magic.name() << ", " << enemy.name() << "???\n\n";
}
}
/* ... */
private:
const std::string d_name;
};
class t_enemy {
t_enemy();
t_enemy(const t_enemy&);
t_enemy& operator=(const t_enemy&);
public:
t_enemy(const std::string& inName) : d_name(inName), d_lifePoints(1000) {
}
virtual ~t_enemy() {
}
const std::string& name() const {
return this->d_name;
}
bool isDead() const {
return 0 >= this->d_lifePoints;
}
const int& lifePoints() const {
return this->d_lifePoints;
}
void hit(const int& points) {
this->d_lifePoints -= points;
}
/* ... */
private:
const std::string d_name;
int d_lifePoints;
};
class t_ghoul : public t_enemy {
public:
static int MaxDaysAwake() {
return 100;
}
t_ghoul(const std::string& inName) : t_enemy(inName), d_bouyancy(prndm(100)), d_proximityToZebra(prndm(100)), d_daysAwake(prndm(MaxDaysAwake())) {
}
const int& bouyancy() const {
return this->d_bouyancy;
}
const int& proximityToZebra() const {
return this->d_proximityToZebra;
}
const int& daysAwake() const {
return this->d_daysAwake;
}
private:
int d_bouyancy;
int d_proximityToZebra;
int d_daysAwake;
};
class t_biclops : public t_enemy {
public:
t_biclops(const std::string& inName) : t_enemy(inName), d_isTethered(prndm(2)), d_amountOfSunblockApplied(prndm(100)) {
}
const bool& isTethered() const {
return this->d_isTethered;
}
const int& amountOfSunblockApplied() const {
return this->d_amountOfSunblockApplied;
}
private:
bool d_isTethered;
int d_amountOfSunblockApplied;
};
class t_magic_operation_table {
public:
typedef void (*t_ghoul_skirmish_function)(t_magic&, t_ghoul&);
typedef void (*t_biclops_skirmish_function)(t_magic&, t_biclops&);
t_magic_operation_table(t_ghoul_skirmish_function ghoulAttack, t_biclops_skirmish_function biclopsAttack) : d_ghoulAttack(ghoulAttack), d_biclopsAttack(biclopsAttack) {
}
void willSkirmish(t_magic& magic, t_ghoul& ghoul) const {
this->d_ghoulAttack(magic, ghoul);
}
void willSkirmish(t_magic& magic, t_biclops& biclops) const {
this->d_biclopsAttack(magic, biclops);
}
private:
t_ghoul_skirmish_function d_ghoulAttack;
t_biclops_skirmish_function d_biclopsAttack;
};
class t_action {
public:
typedef enum t_type {
NoAction = 0,
MagicAction,
ClubAction,
ClassAction
} t_type;
};
class t_attack {
public:
t_attack(const t_action::t_type& actionType, const int& uID, const std::string& inName) : d_actionType(actionType), d_uID(uID), d_name(inName) {
}
virtual ~t_attack() {
}
void reset() {
/* ... */
}
const std::string& name() const {
return this->d_name;
}
private:
t_action::t_type d_actionType;
int d_uID;
std::string d_name;
};
class t_magic : public t_attack {
t_magic();
t_magic(const t_magic&);
t_magic& operator=(const t_magic&);
static void GhoulSkirmishA(t_magic& magic, t_ghoul& ghoul) {
magic.d_accuracy = ghoul.bouyancy() + prndm(16);
magic.d_power = ghoul.proximityToZebra() + prndm(43);
}
static void GhoulSkirmishB(t_magic& magic, t_ghoul& ghoul) {
magic.d_accuracy = ghoul.bouyancy() / magic.flammability() + prndm(32);
magic.d_power = t_ghoul::MaxDaysAwake() - ghoul.daysAwake() + prndm(23);
}
static void BiclopsSkirmishA(t_magic& magic, t_biclops& biclops) {
if (biclops.isTethered()) {
magic.d_accuracy = 90 + prndm(16);
}
else {
magic.d_accuracy = 40 + prndm(11);
}
magic.d_power = biclops.amountOfSunblockApplied() + prndm(17);
}
static void BiclopsSkirmishB(t_magic& magic, t_biclops& biclops) {
if (biclops.isTethered()) {
magic.d_accuracy = 80 + prndm(80);
}
else {
magic.d_accuracy = 50 + prndm(50);
}
magic.d_power = 80 + prndm(30);
}
const t_magic_operation_table* NextOperationTable() {
static const t_magic_operation_table tables[4] = {
t_magic_operation_table(GhoulSkirmishA, BiclopsSkirmishA),
t_magic_operation_table(GhoulSkirmishB, BiclopsSkirmishB),
t_magic_operation_table(GhoulSkirmishB, BiclopsSkirmishA),
t_magic_operation_table(GhoulSkirmishA, BiclopsSkirmishB)
};
return & tables[arc4random() % 4];
}
public:
t_magic(const int& uID, const std::string& inName) : t_attack(t_action::MagicAction, uID, inName), d_power(-1), d_accuracy(-1), d_operationTable(0) {
}
int flammability() const {
return prndm(73);
}
int power() const {
return this->d_power;
}
void reset() {
t_attack::reset();
this->d_power = -1;
this->d_accuracy = -1;
this->d_operationTable = 0;
}
private:
/* assigns this->d_operationTable */
void updateOperationTableForAttack() {
this->d_operationTable = NextOperationTable();
}
public:
void heroWillAttack(const t_hero& hero, t_ghoul& ghoul) {
this->updateOperationTableForAttack();
this->d_operationTable->willSkirmish(*this, ghoul);
std::cout << hero.name() << " vs. " << ghoul.name() << "(lp:" << ghoul.lifePoints() << ")";
this->printState();
}
void heroWillAttack(const t_hero& hero, t_biclops& biclops) {
this->updateOperationTableForAttack();
this->d_operationTable->willSkirmish(*this, biclops);
std::cout << hero.name() << " vs. " << biclops.name() << "(lp:" << biclops.lifePoints() << ")";
this->printState();
}
void printState() {
std::cout << ": Magic { Power: " << this->d_power << ", Accuracy: " << this->d_accuracy << ", Operation Table: " << this->d_operationTable << "}\n";
}
private:
int d_power;
int d_accuracy;
const t_magic_operation_table* d_operationTable;
};
template<typename TEnemy>
void AttackEnemyWithMagic(t_hero& hero, TEnemy& enemy, t_magic& magic) {
if (!enemy.isDead()) {
magic.heroWillAttack(hero, enemy);
hero.attack(enemy, magic);
magic.reset();
}
}
inline void PlayIt() {
t_hero zoe("Zoe");
t_hero aragosta("Aragosta");
t_ghoul ghoul0("Al Paca");
t_ghoul ghoul1("Spud");
t_ghoul ghoul2("Sleepy");
t_biclops biclops("Scimpanzè");
t_magic hemlock(59, "hemlock");
t_magic babyPowder(91, "baby powder");
for (size_t idx(0); idx < 1000; ++idx) {
AttackEnemyWithMagic(zoe, ghoul1, hemlock);
AttackEnemyWithMagic(aragosta, biclops, babyPowder);
AttackEnemyWithMagic(zoe, ghoul2, hemlock);
AttackEnemyWithMagic(aragosta, ghoul0, babyPowder);
}
}
} /* << MONSpiel */
int main(int argc, char* const argv[]) {
#pragma unused(argc)
#pragma unused(argv)
MONSpiel::PlayIt();
return 0;
}
another option is to simply create stores (e.g. a vector), each with a different magic type. then fill a vector to point to objects in those stores. that way, you can just create one contiguous allocation for each type and randomize and weigh as needed. this is useful if your magic objects' sizes vary considerably.