struct student {
std::string name;
std::string class_teacher;
};
int main() {
// https://xkcd.com/327
student bobby_tables{
"Robert'); DROP TABLE Students;--",
"John Thomas"
};
}
class student {
public:
student(std::string name, std::string class_teacher) :
name_(std::move(name)), class_teacher_(std::move(class_teacher))
{
if (!valid_name(name_)) { throw invalid_name(name_); }
if (!valid_name(class_teacher_))
{ throw invalid_name(class_teacher_); }
}
const std::string& name() const;
const std::string& class_teacher() const;
private:
std::string name_;
std::string class_teacher_;
};
If you can not or do not want to throw exceptions, you can use factory methods instead of throwing constructors.
$ ./display_student_details
Please provide student's name: Robert'); DROP TABLE Students;--
Robert'); DROP TABLE Students;-- not foundInvalid name: Robert'); DROP TABLE Students;--
int main()
{
std::vector<student> students = load_students();
std::string name = read_name("Please provide student's name");
if (valid_name(name)) { /* ... */ }
else { std::cerr << "Invalid name: " << name << "\n"; }
// ...or can we trust read_name?
// ...should we assert?
}
class person_name {
public:
// invariant: valid_name(value_);
explicit person_name(std::string val) : value_(std::move(val))
{ if (!valid_name(value_)) { throw invalid_name(value_); } }
explicit operator std::string() const { return value_; }
private:
std::string value_;
};
struct student {
person_name name;
person_name class_teacher;
};
int main()
{
std::vector<student> students = load_students();
person_name
name = read_name("Please provide student's name");
/* ... */
}
struct student {
student_name name;
teacher_name class_teacher;
};
int main()
{
std::vector<student> students = load_students();
student_name
name = read_student_name("Please provide student's name");
/* ... */
}
struct student {
student_name_member_t name;
student_class_teacher_member_t class_teacher;
};
int main()
{
load_students_return_t students = load_students();
read_student_name_return_t
name = read_student_name("Please provide student's name");
/* ... */
}
// Implicit conversion:
// read_student_name_return_t -> student_name_member_t
int main()
{
std::any students = load_students();
std::any
name = read_student_name("Please provide student's name");
/* ... */
}
std::string was initially intuitiveperson_name, student_name, teacher_namestd::stringchar[]std::string?std::string, wierd values are valid, eg. "\a\b".int? The set of values is platform specific.long int, double, etc.std::optional<person_name> when needed
person_name is no longer default-constructiblestd::vector<person_name>?person_name)name member of student type)
struct student {
person_name name;
person_name class_teacher;
};
int main() {
student bobby_tables{
person_name("Bobby Tables"),
person_name("John Thomas")
};
}
enum class name_prefix { /* ... */ }; class person_name { public: // ... private: std::string prefix_; std::vector<std::string> given_names_; std::string family_name_; name_prefix prefix_; std::vector<given_name > given_names_; family_name family_name_; };
enum class name_prefix { /* ... */ };
class person_name {
public:
person_name(
name_prefix prefix,
given_name the_given_name,
family_name the_family_name
);
// ...
private:
name_prefix prefix_;
std::vector<given_name > given_names_;
family_name family_name_;
};
void copy_file( const std::filesystem::path&, const std::filesystem::path& const readable_path &, const writeable_path & ); // copy_file("source", "destination") ? // copy_file("destination", "source") ?
void update_db(const given_name& requestor); { if (!entitled_to_updated_db(requestor)) { throw error(requestor + " is not entitled to updated database"); } // ... } int main() { given_name john("John"); update_db(john + "athan"); }
What should given_name + const char[] do?
meter type wrapping doublemeter / meter be valid?meter / double be valid?std::string → given_nameCharacterSequence<T> ← given_name