c++ string manipulation in setter - c++

Can I manipulate string in setter? For example I want to set it length to 20. Is it that possible and is it the best practice? I get "Non const function is called on const object". Code tried:
void setName(const string &name) {
if (name.size() > 20)
{
name.reserve(20);
}
Employee::name = name;
}

No, in your example name is a const string reference so you can only use const methods on it (basically meaning you can't modify name). The reserve is not a const method. The const string& name means that name is the same string as the caller supplied, the const means that you may not modify it.
What you could do however is to create a copy of name somehow. The most obvious way is maybe to create a copy inside the method, this means that you can keep the function signature (which may be a good thing IMHO):
void setName(const string &name) {
string tname = name;
if (tname.size() > 20)
{
tname.reserve(20);
}
Employee::name = tname;
}
Another way you could do it is to pass name by value which means that the argument will be a copy of what was supplied by the caller (one could argue that one shouldn't alter the signature of the method to reflect the implementation, but that's a matter of opinion):
void setName(string name) { // note no ampersand
What you can't/shouldn't do is to only remove the const because that would mean that the argument is the same string as the caller supplied. It will fail to compile if the caller don't supply a mutable name and if supplied a mutable name the callers copy would change as well (since it's the same).

It seems to me that you are addressing the problem in the wrong way.
The code posted calls reserve when the size of the string passed is higher then 20, but that is a non-binding request to shrink the string (which is const, BTW).
If you want to limit that string member of your class to a particular size while passing a const reference in a setter, all you have to do is copying into it only a substring of the passed string.

I get "Non const function is called on const object"
Of course. That's the whole point of const in the first place!
if (name.size() > 20)
{
name.reserve(20);
}
This does not make sense at all. It literally says: "If the string has more than 20 characters, then make sure that it can internally hold at least 20 characters".
Now, the call may also have the effect that the string's internal capacity shrinks to whatever size greater than 20 it represents to the outside world. For example, if your string has size 30 and its current capacity is 1000, then reserve(20) may shrink the capacity to 30.
However, this is a low-level memory-management concern a beginner typically doesn't or shouldn't care about. If, what I believe, your intention is merely to cut the string, then you need resize.
I would solve your problem like this, plain and simple:
void setName(std::string const& name)
{
this->name = name;
if (this->name.size() > 20)
{
this->name.resize(20);
}
}

You cannot mutate const references: this means that you will not be able to call std::string::reserve as it's not const-qualified.
Consider taking name by value:
void setName(string name)
{
if (name.size() > 20)
{
name.reserve(20);
}
Employee::name = name;
}
In this case, a copy of the string passed to setName will be made, which can be mutated leaving the original unchanged.

Related

Error when creating a string in a function of class

I create a class named Employee, in private, I have a Name as a string . here is my class declaring:
class Employee
{
string Name;
public:
Employee();
void SetName(string);
void StringToEmployee(string);
~Employee();
}
this is definition of StringToEmployee(string) method:
void Employee::StringToEmployee(string s)
{
char *first = s, *end = s+strlen(s), *last = NULL;
last = find(first, end, ',');
string temp(first, last- first);
SetName(temp);
}
The error occurs when I debug to the line string temp(first, last- first), it's seem to the compiler does not allow me to construct a new string in method. cause I have also changed into string temp; then temp.assign(first, last-first). the error still remain. How could I create a new string in a method?
You should be using iterators and taking advantage of the features of the standard library, rather than raw pointers and C-style string functions. Not only will this give you more idiomatic and easier to understand C++ code, but it will also implicitly resolve many of your errors.
First, the implementation of StringToEmployee should be rewritten as follows:
void Employee::StringToEmployee(std::string s)
{
const std::string temp(s.begin(),
std::find(s.begin(), s.end(), ',');
SetName(temp);
}
But since you are not modifying the s parameter and do not need a copy of it, you should pass it by constant reference:
void Employee::StringToEmployee(const std::string& s)
{
const std::string temp(s.begin(),
std::find(s.begin(), s.end(), ',');
SetName(temp);
}
Also, you should consider redesigning your Employee class. Currently, you have a default constructor that creates an invalid Employee object, and then you have member functions that allow you to turn that invalid Employee object into a valid one by settings its members. Instead, you could have a constructor that did all of this initialization for you, in one step. Not only would your code be cleaner and easier to understand, but it would be more efficient, too!
Perhaps something like:
class Employee
{
std::string Name; // name of this employee
public:
Employee(const std::string& name); // create Employee with specified name
void SetName(const std::string& newName); // change this employee's name
~Employee();
};
Employee::Employee(const std::string& name)
: Name(s.begin(), std::find(s.begin(), s.end(), ','))
{ }
void Employee::SetName(const std::string& newName)
{
Name = std::string(s.begin(), std::find(s.begin(), s.end(), ','));
}
Employee::~Employee()
{ }
A couple of quick notes:
You'll see that I always explicitly write out std:: whenever I use a class from the standard library's namespace. This is a really good habit to get into, and it's not really that hard to type an extra 5 characters. It's particularly important because using namespace std; is a really bad habit to get into.
I pass objects (like strings) that I don't need to modify or have a copy of inside of the method by constant reference. This is both easier to reason about, and also potentially more efficient (because it avoids unnecessary copies).
Inside of the constructor, I have used what may appear to be a funny-looking syntax, involving a colon and some parentheses. This is called a member initialization list, and it's something you should get used to seeing. It's the standard way for a class's constructor to initialize its member variables.
For some reason you want to assing std::string to char*.
Judging from other your code, you want to work with raw char array, so, you need to put correct pointers to first and last like this:
char *first = &s[0], *end = (&s[0]) + strlen(s.c_str()), *last = NULL;
And this part:
string temp(first, last- first);
is incorrect, because last - first is pointer, and, as I understand, you want to use std::string(const char*, size_t) constructor. But instead, you are using iterator-based constructor and system is correctly dying, because first pointer is larger, than second one.
As you see, your method is error-prone. I recommend re-do this part of code, using iterators, like this:
void Employee::StringToEmployee(string s)
{
auto found = find(s.begin(), s.end(), ',');
string temp(s.begin(), found);
SetName(temp);
}

How do I access the contents of a std::shared_ptr?

Here is my code.
std::shared_ptr<WSUStudent> WSUStudent::registerStudent(
std::string lastName,
std::string firstName
)
{
auto result = std::shared_ptr<WSUStudent>(new WSUStudent(lastName, firstName));
s_allStudents.insert(&result);
return result;
}
I have successfully managed to change the function so it returns a shared_ptr instead of a normal pointer. I have successfully encapsulated the 'new' statement with a shared pointer, as per the assignment (I think), but the line of code below 'auto' didn't work without the &, and it doesn't work WITH the &. I receive an error stating that there is no matching function call, with or without the &. That line of code is attempting to insert the new student (or a pointer to the new student?) into the list of all students. However the 'insert' method is not locally overridden, so I'm not quite sure what to do here. Error printed below.
/mnt/hgfs/Data Structures and Algorithms/HW04/WSUStudent.cpp:146:32: error: no matching function for call to ‘std::set<WSUStudent*>::insert(std::shared_ptr<WSUStudent>*)’
s_allStudents.insert(&result);
The point of this assignment is to fix memory leaks ('new' statements that don't get deleted with their pointers) by turning normal pointers into weak pointers and shared pointers. The original code is as follows.
WSUStudent *WSUStudent::registerStudent(
std::string lastName,
std::string firstName
)
{
auto result = new WSUStudent(lastName, firstName);
s_allStudents.insert(result);
return result;
}
Am I going about this wrong? I can't get the s_allStudents line to run.
Given the type of s_allStudents, you can use:
s_allStudents.insert(result.get());
However, a better option will be to change type of s_allStudents.
static std::set<std::shared_ptr<WSUStudent>> s_allStudents;
and use:
s_allStudents.insert(result);
Update
The default operator<() of shared_ptr is such that the objects in s_allStudents will be sorted by pointer value. If you would like to sort the objects using a different criterion, you'll need to define a custom functor/function as a parameter of the template.
struct MyCompare
{
bool operator<(shared_ptr<WSUStudent> const& lhs,
shared_ptr<WSUStudent> const& rhs) const
{
// Implement the logic ...
}
};
and use it as:
static std::set<std::shared_ptr<WSUStudent>, MyCompare> s_allStudents;
If you are going to return a std::shared_ptr<WSUStudent> then you are returning ownership rights to the object you created - meaning someone else will be trying to delete it at some point.
Unless you also keep ownership that means your pointer could get deleted before you are finished with it. So you need to also store std::shared_ptr in your static set:
I am guessing at how you are using this class but what I mean goes something like this:
class WSUStudent
{
// you really need to store shared pointers in here
static std::set<std::shared_ptr<WSUStudent>> s_allStudents;
std::string lastName;
std::string firstName;
// only the static factory function can make students
WSUStudent(
const std::string& lastName, // passing by const& is more usual (idiomatic)
const std::string& firstName)
: lastName(lastName)
, firstName(firstName)
{
}
public:
static std::shared_ptr<WSUStudent> registerStudent(
const std::string& lastName,
const std::string& firstName);
};
std::shared_ptr<WSUStudent> WSUStudent::registerStudent(
const std::string& lastName,
const std::string& firstName
)
{
auto result = std::shared_ptr<WSUStudent>(new WSUStudent(lastName, firstName));
// put the shared student in your set
s_allStudents.insert(result);
return result;
}
// define your set
std::set<std::shared_ptr<WSUStudent>> WSUStudent::s_allStudents;
int main ()
{
// make students
auto s = WSUStudent::registerStudent("bill", "bob");
// all deletions should be in order
}

How to iterate through all elements of set C++

[UPDATE: My problem is solved! Lots of thanks to Mike Seymour and Niall and all you guys!]
My code has errors in the for loop and I do not know how to fix it :(
MyClass::ITECH7603Class(set<Student>* students) {
/* Initialize dynamically the group field */
group = new map<string, Student>();
for (set<Student>::iterator it = students->begin(); it != students->end(); it++) {
addStudent(it);
}
}
void MyClass::addStudent(Student* studentPtr) {
string fullName = studentPtr->getName() + " " + studentPtr->getSurname();
group->insert(pair<string, Student>(fullName, *studentPtr));
}
So the main idea is to loop through all students in the set, and add each student into a map group. Any help? Thank you very much!
for (set<Student>::iterator it = students->begin; it != students->end; it++) {
addStudent(it);
}
should be:
for (set<Student>::iterator it = students->begin(); it != students->end(); it++) {
//^^ //^^
addStudent(it);
}
addStudent takes a pointer, while it is an iterator, so can't be passed directly.
You should change addStudent to take either a value or a pointer/reference to const:
// option 1
void addStudent(Student);
addStudent(*it);
// option 2
void addStudent(Student const &);
addStudent(*it);
// option 3
void addStudent(Student const *);
addStudent(&*it);
If, as you say in a comment, you must leave it taking a mutable pointer, then you'll need some grotesquery to deal with the fact that elements of the set are immutable:
// nasty option
addStudent(const_cast<Student*>(&*it));
// slightly less nasty option
Student copy = *it;
addStudent(&copy);
Beware that the first option will give undefined behaviour if the function uses the dodgy pointer to make any modification to the Student object stored in the set. The second makes a temporary copy, which can be modified without breaking the set. This is fine as long as addStudent only stores a copy of the object passed to it, not the pointer itself, which will become invalid when copy is destroyed.
In c++11 you can use range for sytax:
for (const auto &student : *students)
{
addStudent(it);
}
Then change addStudent function signature to accept reference:
void MyClass::addStudent(const Student &student) {
While you've gotten answers that "fix" your code to the extent of compiling and producing results that you apparently find acceptable, I don't find them very satisfying in terms of code style. I would do this job rather differently. In particular, my code to do this wouldn't have a single (explicit) loop. If I needed to do approximately what you're asking for, I'd probably use code something like this:
std::pair<std::string, Student> make_mappable(Student &stud) {
return std::make_pair(stud.getName() + " " + stud.getSurName(), stud);
}
std::map<std::string, Student> gen_map(std::set<Student> const &input) {
std::map<std::string, Student> ret;
std::transform(input.begin(), input.end(),
std::inserter(ret, ret.end()),
make_mappable);
return ret;
}
There definitely would not be any new in sight, nor would there be any passing a pointer to a Student.
OTOH, since the data you're using as the key for your map is data that's already in the items in the set, it may more convenient all around to continue to use a set, and just specify a comparison function based on the student's name:
struct by_given_name {
bool operator()(Student const &a, Student const &b) const {
if (a.getName() < b.getName())
return true;
if (b.getName() < a.getName())
return false;
return a.getSurName() < b.getSurName();
}
};
std::set<Student, by_given_name> xform(std::set<Student> const &in) {
return std::set<Student, by_given_name>{in.begin(), in.end()};
}
For what its worth, a Live Demo of the latter.
Whether the latter is practical will typically depend on one other factor though: your ability to create a Student from only a name/surname. If you can't do that, searching by name will be inconvenient (at best), so you'd want to use a map.
I realize this probably isn't much (if any) help in completely what's apparently home-work for a class--but even if your class prevents you from actually turning in decent code, it seems worthwhile to me to at least try to learn to write decent code in addition to what it requires. If you do pass the class and get a job writing code, you'd probably rather your coworkers didn't want to hurt you.

Copy string value into a class field?

I'm new to and learning C++. I know a fair amount of Java and some C.
What I want to do is to create an immutable name class that takes in a string value, copies that string to a class field and then eventually hashes it to an ID that can be parsed much more efficiently than a string.
I'm hitting a wall due to a general lack of knowledge of C++ strings. Here's what I have so far...
#pragma once
#include <string>
class Name
{
public:
Name(std::string s);
~Name(void);
int getId();
std::string getName();
private:
int id;
std::string name;
};
and...
#include "Name.h"
Name::Name(std::string s)
{
}
So what I want to do is store the value of s, passed in by the constructor in the "name" private field. As far as I know a new string object must be created and then the value of s must be copied into it.
I also think that the argument s can and should be a string pointer instead of a string object (to prevent an unnecessary copy from occurring). If I'm right then the constructor should look like the following, right?
Name::Name(std::string &s) { ... }
In this case, nothing would need to be done special when passing in a name? IE.
Name n = new Name("Cody");
is perfectly valid? Actually I'm not sure since "Cody" to my knowledge is a constant string or something like that.
So if I'm all on the right track, then what is the proper way to actually copy the value? I'm thinking this is appropriate but I'm not sure.
#include "Name.h"
Name::Name(std::string s)
{
name = new string(s);
}
Thanks for the help in advance, I know it's a basic question but I'm slowly making baby steps into the C++ world. :) - Cody
You are close, your code can be like this after a little massage:
class Name
{
public:
Name(const std::string& s); // add const and reference
~Name(void);
int getId() cosnt; // add const
std::string getName() const; // add const
private:
int id;
std::string name;
};
Name.cpp
Name::Name(const std::string& s):name(s)
{
}
Here :name(s) is called member initializer list.
Name n = new Name("Cody"); is perfectly valid? Actually I'm not sure
since "Cody" to my knowledge is a constant string or something like
that.
No, n is not pointer, it's not like java you need to new for every object. In C++, you do
Name n("Cody");
This will call Name(const std::string& s) to initialize object n and initialize name string with "Cody".
Note: variable n has automatic storage duration, it will be destroyed if it goes out of scope.
To let n on dynamic storage duration, you need to use new/delete pair:
Name *pn = new Name("Cody");
delete pn;
or use smart pointers, you no need to call delete n_ptr; as n_ptr will be destroyed when it goes out of scope as well:
#include <memory>
std::shared_ptr<Name> n_ptr(new Name("Cody"));
EDIT:
To use Name class in other classes, it's the same way when you use string in Name class, you don't have to use pointers.
class TestName
{
public:
TestName(const Name& n):name_(n){ }
private:
Name name_;
};
TestName tn("Cody");
You should use a constant reference to std::string here.
As you said, it would prevent unnecessary copies.. But then why not just a pointer or a constant pointer?
A constant reference would allow you to pass to your function some arguments that would implicitly call the right std::string constructor.
So, in a nutshell, you could do that:
Name::Name(const std::string& s)
{
this->name = s;
}
// Or even better..
Name::Name(const std::string& s):
name(s)
{
}
int main(void)
{
Name nick("hello");
return 0;
}
You can find out about every std::string's constructors on its cplusplus.com's sheet.

l-value substr method in C++

I want to create a substr method in C++ in a string class that I made.
The string class is based on C-style string of course, and I take care of the memory management.
I want to write a substr(start, length) function that can work on the regular way:
CustomString mystring = "Hello";
cout << mystring.substr(0,2); // will print "He"
And also in this way:
mystring.substr(1,3) = "DD"; // mystring will be "HDDo"
Notice that even though I get a 3 chars long sub-string, I put in the assignment only 2 chars and the output string will be HDDo, still.
Any idea how to get this done?
Thanks!
To support that, you'll probably have to write your substr() to return a proxy object that keeps track of what part of the original string is being referred to. The proxy object will overload operator=, and in it will replace the referred-to substring with the newly assigned one.
Edit in response to comments: the idea of a proxy is that it's similar enough to the class for which it's a proxy that returning a proxy is still a closed operation -- i.e. from the user's viewpoint, all that's visible is the original type of object, but it has capabilities that wouldn't be possible (or would be much more difficult to implement) without the proxy. In this case, we the proxy class would be private to the string class, so the user could never create an instance of the proxy class except as a temporary. That temporary can be used to modify its parent string if you assign to it. Using the proxy in any other way just yields a string.
As to what this buys you over attempting to do it all inside the original string: each proxy object is a temporary object -- the compiler can/will/does keep track of how to create temporaries as needed, destroys them properly at the end of a full expression, etc. The compiler also keeps track of what substring a particular assignment refers to, automatically converts one to a string when we try to use its value, and so on. Simply put, the compiler handles nearly all the hard work involved.
Here's some working code. The surrounding string class is pretty minimal (e.g. it has no searching capability). I'd expect to add a fair amount to a useful version of the string class. The proxy class, however, is complete -- I wouldn't expect to see it change much (if at all) in a feature-complete version of the string class.
#include <vector>
#include <algorithm>
#include <iostream>
#include <iterator>
class string {
std::vector<char> data;
public:
string(char const *init) {
data.clear();
data.assign(init, init+strlen(init));
}
string(string const &s, size_t pos, size_t len) {
data.assign(s.data.begin()+pos, s.data.begin()+pos+len);
}
friend class proxy;
class proxy {
string &parent;
size_t pos;
size_t length;
public:
proxy(string &s, size_t start, size_t len) : parent(s), pos(start), length(len) {}
operator string() { return string(parent, pos, length); }
proxy &operator=(string const &val) {
parent.data.erase(parent.data.begin()+pos, parent.data.begin()+pos+length);
parent.data.insert(parent.data.begin()+pos, val.data.begin(), val.data.end());
return *this;
}
};
proxy substr(size_t start, size_t len) {
return proxy(*this, start, len);
}
friend std::ostream &operator<<(std::ostream &os, string const &s) {
std::copy(s.data.begin(), s.data.end(), std::ostream_iterator<char>(os));
return os;
}
};
#ifdef TEST
int main() {
string x("Hello");
std::cout << x << std::endl;
std::cout << x.substr(2, 3) << std::endl;
x.substr(2, 3) = "DD";
std::cout << x << std::endl;
return 0;
}
#endif
Edit 2:
As far as substrings of substrings go, it depends. The one situation that's not currently covered is if you want to assign to a substring of a substring, and have it affect the original string. If you want something like x=y.substr(1,4).substr(1,2); it'll work fine as-is. The first proxy will be converted to a string, and the second substr will be invoked on that string.
If you want: x.substr(1,4).substr(1,2) = "whatever"; it won't currently work. I'm not sure it accomplishes much, but on the assumption that it does, the addition to support it is fairly minimal -- you'd add a substr member to proxy:
proxy substr(size_t start, size_t len) {
return proxy(parent, pos+start, len);
}
Presumably you want substr to return a string, rather than some other proxy class. You'd therefore need to make your string class capable of holding a pointer to its own copy of the string data and also a pointer to another string object that it was created from (as the return value of substr), along with information about which part of the string it was created from.
This might get quite complicated when you call substr on a string returned from another call to substr.
The complexity is probably not worth the attractiveness of the interface.
The first requirement is simple; look up operator's standard implementation.
Loosely, c_string& substr(int, int)
The second part, not so much, I don't think. It'll look similar, I believe. However, I'll think about it and get back to you over the weekend.