c++ - C++ 11-修改结构 vector 中的结构成员

标签 c++ c++11 for-loop embedded unique-ptr

我已经在SO的其他地方和此处搜索了实现此目的的方法,但没有找到解决我的问题和疑虑的任何答案。

约束:

  • 我在嵌入式设备上使用 C++ 11
  • 我不能使用 std::string
  • 我不能使用 std::make_unique() (但是我可以将std::unique_ptrnew一起使用)
  • 我不能使用 strcpy_s()

  • 我遇到了

    我遇到的主要问题是,在AvailableZones::upsertZone方法中,如果该区域尚不存在,我想将其添加到 vector 中(使用name参数作为“键”)。如果存在,我想更新该区域的温度和湿度成员。 “添加”部分有效,但是更新部分无效。

    我的下一个问题是AvailableZones::findZone成员。我希望能够返回一个区域,以便调用者不负责释放/删除返回的值。

    关注点:

    作为C++的新手,我敢肯定我没有以正确的C++ 11方式做很多事情。我愿意(实际上渴望)任何/所有指导。
    AvailableZones::findZone方法中,我想返回我拥有的Zone,而不是创建副本或使用new / malloc。看来我需要使用常规的for / while循环?我已经看过一些迭代器代码,但是看起来很困惑/复杂,但是我不确定使用迭代器是否可以解决这个问题。

    最佳实践相关问题:
  • Zone结构的析构函数中,如果我使用delete,它将导致和
    运行代码时发生异常。我显然做错了。
  • Zone结构中,我可以使name成员成为std::unique_ptr吗?如果
    又怎样?我已经尝试了很多方法,但是我都无法做到
    进行编译或工作。
  • 是否有更好的方法来实现Zone构造函数?

  • 代码

    我已经在代码中添加了注释,以解释该方法的意图以及需要帮助的地方。
    #include "stdafx.h"
    #include <iostream>
    #include <assert.h>
    #include <memory>
    #include <vector>
    
    using namespace std;
    
    struct Zone {
        Zone() {}
        Zone(const char* name, const float temperature, const float humidity)
        {
            auto bufferSize = snprintf(NULL, 0, "%s", name);
            this->name = new char[bufferSize + 1];
            strcpy(this->name, name);
            this->temperature = temperature;
            this->humidity = humidity;
        }
    
        ~Zone() {
            // deleting name here causes an Exception
            //delete [] name;
        }
    
        char* name = nullptr;
        float temperature = 0.0f;
        float humidity = 0.0f;
    };
    
    class AvailableZones {
    public:
        AvailableZones::AvailableZones() {
            m_zoneVec = std::vector<Zone>();
        }
    
        ~AvailableZones() {
        }
    
        /*
            Using Arguments, add a Zone to the private zoneVec member is it does not exist
            If is does exist (names of zones are unique and used as the "key"), then update
            the temperature and humidity of the existing zone with those in the arguments
        */
        void AvailableZones::upsertZone(const char *name, const float temperature, const float humidity) {
    
            for (auto zone : m_zoneVec) {
                if (strcmp(zone.name, name) == 0) {
                    zone.temperature = temperature;
                    zone.humidity = humidity;
                    return;
                }
            }
    
            m_zoneVec.push_back(Zone(name, temperature, humidity));
        }
    
        /*
            Given a Zone name, find the zone and return it
            If a Zone with the given name does not exist
            return a nullptr
        */
        const Zone *AvailableZones::findZone(const char *name) const {
    
            for (auto zone : m_zoneVec) {
                if (strcmp(zone.name, name) == 0) {
                    // I know this is not correct.
                    // How would I do this, without using "new" and thus
                    // forcing the caller to be responsible for deleting?
                    return &zone;
                }
            }
    
            return nullptr;
        }
    
    private:
        std::vector<Zone> m_zoneVec;
    };
    
    
    int main()
    {
        auto livingRoom = "Living Room";
        AvailableZones availableZones;
        availableZones.upsertZone("Master Bedroom", 72.0f, 50.0f);
        availableZones.upsertZone(livingRoom, 70.0f, 48.0f);
        availableZones.upsertZone("Study", 68.0f, 46.0f);
    
        auto foundZone = availableZones.findZone(livingRoom);
        cout << foundZone->name << endl;
        cout << foundZone->temperature << endl;
        cout << foundZone->humidity << endl;
    
        assert(strcmp(livingRoom, foundZone->name) == 0);
        assert(70.0f == foundZone->temperature);
        assert(48.0f == foundZone->humidity);
    
        availableZones.upsertZone(livingRoom, 74.0f, 52.0f);
    
        foundZone = availableZones.findZone(livingRoom);
    
        assert(strcmp(livingRoom, foundZone->name) == 0);
        assert(74.0f == foundZone->temperature);
        assert(52.0f == foundZone->humidity);
    
        return 0;
    }
    

    编辑:
    下面的代码实现了@ max66以及@ Vaughn Cato和@Artemy Vysotsky提出的建议。该代码现在按照我的要求工作。进行了以下更改:
  • 基于范围的for循环正在使用引用(或const引用为
    可能是这样)。默认情况下,基于范围的循环通过以下方式提供元素
    值(@Vaughn Cato也建议)
  • upsertZone方法中,我使用emplace_back(),以便在容器提供的位置就位创建Zone实例。使用push_back()(早期代码)创建了一个临时副本,只是被扔掉了(我假设是因为我没有实现move构造函数)。
  • 使用strlen(由@ArtemyVysotsky建议)相对于snprintf,允许我在Zone构造函数中使用初始化列表。
  • 实现了副本分配运算符Zone &operator=(Zone &other)
  • 实现副本构造函数
  • 实现了移动分配运算符Zone &operator=(Zone &&other)
  • 实现了Move构造函数

  • 发现:
    每次我向 vector 添加一个元素。先前的元素被“复制”到新的容器位置,而先前的元素被破坏。我希望他们能被感动而不是被复制。我不确定是否需要执行某些操作来确保将其移动而不是复制。

    进一步更新
    看起来,要使用Move构造函数,它必须是noexcept。完成此操作后,没有任何更改的相同代码现在将使用Move而不是Copy。

    根据建议的工作代码
    struct Zone {
        Zone() {}
        Zone(const char* name, const float zoneTemperature, const float zoneHumidity)
            :name(strcpy(new char[strlen(name) + 1], name))
            ,temperature{ zoneTemperature }
            ,humidity {zoneHumidity}
        {
            cout << "Zone constructor: " << name << endl;
        }
        /* Copy Constructor */
        Zone(Zone const& other)
            :name(strcpy(new char[strlen(other.name) + 1], other.name))
            ,temperature{ other.temperature }
            ,humidity{ other.humidity }
        {
            std::cout << "In Zone Copy Constructor. name = " << other.name << ". Copying resource." << std::endl;
        }
        /* Move Constructor */
        Zone(Zone&& other) noexcept
            : name(nullptr)
            , temperature(0.0f)
            , humidity(0.0f)
        {
            std::cout << "In Zone Move Constructor. name = "    << other.name << ". Moving resource." << std::endl;
    
            // Copy the data pointer and its length from the   
            // source object.  
            name = other.name;
            temperature = other.temperature;
            humidity = other.humidity;
    
            // Release the data pointer from the source object so that  
            // the destructor does not free the memory multiple times.  
            other.name = nullptr;
            other.temperature = 0.0f;
            other.humidity = 0.0f;
        }
    
        ~Zone()
        {
            cout << "Zone Destructor: " << name << endl;
            delete[] name;
        }
    
        /* Copy Assignment Operator */
        Zone& operator=(Zone const& other) {
            std::cout << "In Zone Copy Assignment Operator. name = " << other.name << "." << std::endl;
            Zone tmpZone(other);
            std::swap(name, tmpZone.name);
            std::swap(temperature, tmpZone.temperature);
            std::swap(humidity, tmpZone.humidity);
            return *this;
        }
    
        /* Move Assignment Operator */
        Zone& operator=(Zone&& other) noexcept {
            std::cout << "In Zone Move Assignment Operator. name = " << other.name << "." << std::endl;
    
            if (this != &other)
            {
                // Free the existing resource.  
                delete[] name;
    
                // Copy the data pointer and its length from the   
                // source object.  
                name = other.name;
                temperature = other.temperature;
                humidity = other.humidity;
    
                // Release the data pointer from the source object so that  
                // the destructor does not free the memory multiple times.  
                other.name = nullptr;
                other.temperature = 0.0f;
                other.humidity = 0.0f;
            }
    
            return *this;
        }
    
        char* name = nullptr;
        float temperature = 0.0f;
        float humidity = 0.0f;
    };
    
    class AvailableZones {
    public:
        AvailableZones::AvailableZones() {
            m_zoneVec = std::vector<Zone>();
        }
    
        ~AvailableZones() {
        }
    
        /*
            Using Arguments, add a Zone to the private zoneVec member is it does not exist
            If is does exist (names of zones are unique and used as the "key"), then update
            the temperature and humidity of the existing zone with those in the arguments
        */
        void AvailableZones::upsertZone(const char *name, const float temperature, const float humidity) {
    
            for (auto &zone : m_zoneVec) {
                if (strcmp(zone.name, name) == 0) {
                    zone.temperature = temperature;
                    zone.humidity = humidity;
                    return;
                }
            }       
    
            m_zoneVec.emplace_back(name, temperature, humidity);
        }
    
        /*
            Given a Zone name, find the zone and return it
            If a Zone with the given name does not exist
            return a nullptr
        */
        const Zone *AvailableZones::findZone(const char *name) const {
    
            for (auto const &zone : m_zoneVec) {
                if (strcmp(zone.name, name) == 0) {
                    return &zone;
                }
            }
    
            return nullptr;
        }
    
    private:
        std::vector<Zone> m_zoneVec;
    };
    
    void doWork() {
        static_assert(std::is_nothrow_move_constructible<Zone>::value, "Zone should be noexcept MoveConstructible");
        auto livingRoom = "Living Room";
        AvailableZones availableZones;
        availableZones.upsertZone("Master Bedroom", 72.0f, 50.0f);
        availableZones.upsertZone(livingRoom, 70.0f, 48.0f);
        availableZones.upsertZone("Study", 68.0f, 46.0f);
    
        auto foundZone = availableZones.findZone(livingRoom);
        cout << foundZone->name << endl;
        cout << foundZone->temperature << endl;
        cout << foundZone->humidity << endl;
    
        assert(strcmp(livingRoom, foundZone->name) == 0);
        assert(70.0f == foundZone->temperature);
        assert(48.0f == foundZone->humidity);
    
        availableZones.upsertZone(livingRoom, 74.0f, 52.0f);
    
        foundZone = availableZones.findZone(livingRoom);
        assert(strcmp(livingRoom, foundZone->name) == 0);
        assert(74.0f == foundZone->temperature);
        assert(52.0f == foundZone->humidity);
    
        foundZone = availableZones.findZone("Non Existent Zone");
        assert(foundZone == nullptr);
    }
    
    
    int main()
    {
        doWork();
        return 0;
    }
    

    最佳答案

    如果您不检查返回的指针是否为nullptr,则从findZone()返回nullptr没有用处(并且很危险)

    auto foundZone = availableZones.findZone(livingRoom);
    cout << foundZone->name << endl;
    

    使用findZone()有很多不同的方法可以解决您的问题;为了避免出现问题,我建议避免使用指针并返回该元素的副本(但是您必须编写一个副本构造函数);但是,如果您确实要返回一个指针,则可以按以下方式重写该函数
    Zone const * findZone(const char *name) const {
        for ( auto const & zone : m_zoneVec) {
            if (strcmp(zone.name, name) == 0) {
                return & zone;
            }
        }
    
        return nullptr;
    }
    

    关键是使用const(因为该方法是const)引用到m_zoneVec中的元素(auto const & zone : m_zoneVec;观察&),而现在您使用临时 复制(而不是auto zone : m_zoneVec; 。因此,您可以返回 vector 元素的指针,而不是立即被销毁的临时对象的指针。

    您在&中遇到了完全相同的问题:循环测试和(以防万一)修改 vector 中元素的复制
        for (auto zone : m_zoneVec) {  // DANGER: zone is a **copy**
            if (strcmp(zone.name, name) == 0) {
                zone.temperature = temperature;
                zone.humidity = humidity;
                return;
            }
        }
    

    因此,您可以修改立即销毁的副本。原始的upsertZone()保持不变。

    您必须修改引用
        for (auto & zone : m_zoneVec) {  // with & zone is a **reference**
            if (strcmp(zone.name, name) == 0) {
                zone.temperature = temperature;
                zone.humidity = humidity;
                return;
            }
        }
    

    但是,非常重要的是,您应该创建一个副本构造函数(也许还可以创建一个移动构造函数);一个复制构造函数,它使用Zone分配一个新的名称数组;否则,将使用默认的复制构造函数来复制指针。

    因此,例如,当你写
    m_zoneVec.push_back(Zone(name, temperature, humidity));
    

    创建一个临时对象,然后通过创建副本和销毁该临时对象将其推入 vector 。如果启用了new析构函数中的delete,则销毁Zone和临时delete以及 vector 中的值将使用指向空闲区域的name。从这一点来看,程序的行为是不确定的,无论如何,当name被破坏时(在程序的末尾),availableZone会被调用在->崩溃之前被删除的指针上!

    您可以使用delete避免插入副本(我建议这样做)
    m_zoneVec.emplace_back(name, temperature, humidity);
    

    但是在emplace_back()中添加更多元素会导致 vector 重定位,因此移动副本并销毁。

    如果可以使用m_zoneVec,我想您也可以使用std::unique_ptr

    显式复制和移动构造函数创建的一种可能替代方法是使用插入到智能指针中的std::shared_ptr(根据name的使用,可以是唯一的或共享的)。

    关于c++ - C++ 11-修改结构 vector 中的结构成员,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45898854/

    相关文章:

    c++ - 如何使用 std::exception —— 堆栈跟踪和内存泄漏

    c++ - 结合 GMPXX 和 C++11 及更高版本

    c - 二维数组中的意外输出

    c++ - 在 <class> 对象的 vector 中添加新元素时使用非默认构造函数

    c++ - 用模板重写的类会使程序变慢(在运行时)

    c++ - 智能指针和派生类

    c++ - union 成员的析构函数是否被调用

    c - 带有 scanf - C 变量输入的循环条件

    Java For循环只执行一次

    c++ - 隐藏其他运营商一段时间