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_name
std::string
char[]
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 double
meter / meter
be valid?meter / double
be valid?std::string
→ given_name
CharacterSequence<T>
← given_name