Vector3 ควรสืบทอดจาก Vector2 หรือไม่


18

ฉันกำลังสร้างคู่ของชั้นเรียนVector2(X & Y) และVector3(X, Y และ Z) แต่ผมไม่ทราบว่าจะทำให้การVector3สืบทอดมาจากVector2หรือไม่ว่าการดำเนินการอีกครั้งตัวแปรสมาชิกm_xและm_yอีกครั้ง? ข้อดีและข้อเสียของแต่ละด้านคืออะไร

แก้ไข:ฉันใช้ C ++ (VS2010)


1
ทำไมไม่เขียนคลาสเวกเตอร์ทั่วไปสำหรับเวกเตอร์ขนาด n และจากนั้น (หากจำเป็น) สืบทอดคลาสเวกเตอร์ 2 และคลาสเวกเตอร์ 3 คุณสามารถใช้เทมเพลตสำหรับคลาสทั่วไปและสืบทอดรุ่นสำหรับเวกเตอร์จำนวนเต็มและเวกเตอร์ลอยได้เช่นกัน แก้ไข: ทำไมคุณไม่ใช้ห้องสมุดคณิตศาสตร์ที่ดีที่สุด?
danijar

5
โดยยืดของจินตนาการ "Vector3 เป็น Vector2" ไม่มีพวกเขาสามารถทั้งสืบทอดจากพ่อแม่ VectorN แม้ว่า
Wim

1
ใช่ แต่คุณอาจต้องใช้ตารางเสมือนและนั่นเป็นหนึ่งในกรณีที่ค่าใช้จ่ายเกี่ยวกับรันไทม์และหน่วยความจำมีความสำคัญ ตามหลักแล้ว a Vector3ควรจะเท่ากับ 3 floatsเท่าที่หน่วยความจำเกี่ยวข้อง ไม่ได้บอกว่ามันเป็นไปไม่ได้เลยที่ฉันไม่เคยเห็นมันมาก่อนในเครื่องมือการผลิต
Laurent Couvidou

2
ใช่ฉันคิดอย่างนั้น ตราบใดที่คุณไม่ต้องการอะไรอีกfloatsแล้ว คุณรู้ไหม YAGNI KISS ทุกอย่างนั้น Vector2, Vector3และVector4มรดกและไม่floatsเพียง แต่เป็นจริงมาตรฐาน de facto ในเครื่องมือเกม
Laurent Couvidou

1
ฉันหวังว่าคุณหมายถึงtypedef float real;;)
ทำเครื่องหมายอินแกรม

คำตอบ:


47

ไม่ไม่ควร สิ่งเดียวที่คุณจะใช้จากมรดกคือxและyส่วนประกอบ วิธีการที่ใช้ในVector2ชั้นเรียนจะไม่เป็นประโยชน์ในVector3ชั้นเรียนพวกเขามีแนวโน้มที่จะรับข้อโต้แย้งที่แตกต่างกันและดำเนินการกับตัวแปรสมาชิกจำนวนอื่น


+1, ฉันควรให้ความสำคัญกับป๊อปอัพมากขึ้นดังนั้นฉันจะไม่เขียนเนื้อหาที่ซ้ำซ้อน
Matsemann

8
มากเกินไปมรดกคลาสสิก A Vector3IS-NOT-A Vector2(ดังนั้นจึงไม่ควรสืบทอด) แต่AppleIS-A Fruit(ดังนั้นจึงอาจสืบทอด) หากคุณบิดพอใจของคุณเป็นVector3HAS-A Vector2ในนั้น แต่การสูญเสียประสิทธิภาพการทำงานและความยากลำบากในการเข้ารหัสหมายถึงคุณจะเขียนแยกชั้นเรียนอย่างสมบูรณ์สำหรับและVector3 Vector2
bobobobo

แต่คุณสามารถ (ในความคิดของฉันควร) เขียนคลาสเวกเตอร์ n- มิติเพื่อรับเวกเตอร์ 2 มิติและเวกเตอร์ 3 มิติจากนั้น
danijar

8

มีสิ่งที่อยากรู้อยากเห็นที่คุณสามารถทำได้กับ C ++ (คุณไม่ได้ระบุภาษาและคำตอบนี้ส่วนใหญ่เป็นเพราะฉันคิดว่ามันเป็นเรื่องดีที่จะเห็นทางเลือกแม้ว่าฉันจะไม่เชื่อว่านี่เป็นประโยชน์ในกรณีส่วนใหญ่)

การใช้แม่แบบคุณสามารถทำสิ่งนี้:

template <class T, class S, int U>
class VectorN
{
    protected:
        int _vec[U];
    public:
        S& operator+=(const S c)
        {
            for(int i = 0; i < U; i++)
            {
                _vec[i] += c.at(i);
            }
            return (S&)*this;
        }
        int at(int n) const
        {
            return _vec[n];
        }
};

template <class T>
class Vec2 : public VectorN<T,Vec2<T>,2>
{
    public:
        T& x;
        T& y;
        Vec2(T a, T b) : x(this->_vec[0]), y(this->_vec[1])
        {
            this->_vec[0] = a;
            this->_vec[1] = b;
        }
};

template <class T>
class Vec3 : public VectorN<T,Vec3<T>,3>
{
    public:
        T& x;
        T& y;
        T& z;
        Vec3(T a, T b, T c) : x(this->_vec[0]), y(this->_vec[1]), z(this->_vec[2])
        {
            this->_vec[0] = a;
            this->_vec[1] = b;
            this->_vec[2] = c;
        }
};

และสิ่งนี้สามารถใช้เช่นนี้:

int main(int argc, char* argv[])
{

    Vec2<int> v1(5,0);
    Vec2<int> v2(10,1);

    std::cout<<((v1+=v2)+=v2).x;
    return 0;
}

อย่างที่ฉันบอกไปฉันไม่คิดว่ามันจะมีประโยชน์และมันอาจจะทำให้ชีวิตคุณซับซ้อนขึ้นเมื่อคุณพยายามที่จะใช้จุด / ปกติ / สิ่งอื่น ๆ และพยายามที่จะใช้สามัญกับเวกเตอร์จำนวนเท่าใดก็ได้


ใช่ความสามัญทั้งหมดดูดีมากเพียงแค่คุณต้องการ 3-vector มาตรฐานที่มี 3 องค์ประกอบจุดลอย - วงเล็บมุมทั้งหมดจะทำให้Vector3f vค่อนข้าง bloaty มากขึ้นVector3<float> v
bobobobo

@bobobobo ใช่ฉันเห็นด้วย คลาสเวกเตอร์ของฉันมักจะเป็น vec2 และ vec3 ที่ไม่มีพาเรนต์ แต่ยังคงทำให้เป็นเทมเพลต ถ้าการเขียน Vector3 <float> ทำให้คุณรำคาญคุณก็สามารถพิมพ์มันได้เสมอ
Luke B.

.. และตอนนี้อาร์กิวเมนต์ C ของโปรแกรมเมอร์ .. "แต่สิ่งที่เกี่ยวกับเวลารวบรวมเพิ่มขึ้นสำหรับการใช้แม่แบบ?" ในกรณีนี้คุ้มหรือไม่
bobobobo

@bobobobo ฉันไม่เคยมีปัญหาใด ๆ กับการรวบรวมครั้ง: P แต่ฉันไม่เคยทำงานในโครงการที่รวบรวมเวลาจะเป็นปัญหา หนึ่งสามารถยืนยันว่าเวลารวบรวมเหตุผลที่ไม่ได้ใช้ลอยเมื่อคุณต้องการจำนวนเต็ม
ลุคบี

@bobobobo ด้วยอินสแตนซ์ที่ชัดเจนและไม่รวมไฟล์อินไลน์ของคุณในส่วนหัวเวลารวบรวมจะไม่แตกต่างกัน นอกจากนี้เทมเพลตวงเล็บมุมเทมเปอร์อยู่typedefห่างออกไปเพียงหนึ่ง
Samaursa

7

โดยไม่คำนึงถึงความเร็วคำถามแรกที่คุณควรถามตัวเองเมื่อทำการรับมรดกใด ๆคือถ้าคุณกำลังจะใช้พวกเขา polymorphically โดยเฉพาะอย่างยิ่งมีสถานการณ์ใดบ้างที่คุณสามารถเห็นตัวเองโดยใช้ a Vector3ราวกับว่ามันเป็นVector2(ซึ่งโดยการสืบทอดจากมันคุณกำลังพูดอย่างชัดเจนว่า Vector3 "เป็น -a" Vector2)

ถ้าไม่เช่นนั้นคุณไม่ควรใช้การสืบทอด คุณไม่ควรใช้การสืบทอดเพื่อแบ่งปันรหัส นั่นคือสิ่งที่องค์ประกอบและฟังก์ชั่นภายนอกมีไว้เพื่อมิให้คุณแชร์รหัสระหว่างกัน

ที่ถูกกล่าวว่าคุณอาจต้องการวิธีที่ง่ายต่อการแปลง Vector3เพื่อVector2s และในกรณีที่คุณสามารถเขียนเกินที่ผู้ประกอบการโดยปริยายจะตัดไปVector3 Vector2แต่คุณไม่ควรรับช่วง


ขอบคุณฉันคิดว่าสิ่งที่เน้นถึงปัญหาคือฉันได้ดูสิ่งนี้จากมุมมอง "การแบ่งปันรหัส" (กล่าวคือไม่ต้อง "พิมพ์ค่า" X & Y อีกครั้ง)
ทำเครื่องหมายอินแกรม

+1 คำตอบที่ยอดเยี่ยมไม่มีการใช้ polymorphic ระหว่างเวกเตอร์ที่มีขนาดแตกต่างกัน
ลุคบี

นี่คือสิ่งที่ยิ่งใหญ่ที่สุดที่ฉันจะเพิ่มเข้าไปในคำตอบของฉันเอง - +1 อย่างแน่นอน (แม้ว่าจะมีสถานการณ์แปลก ๆ ที่ฉันสามารถจินตนาการได้ว่าต้องการ polymorphism - เช่นเกม 2.5m 'heightmap' ที่สิ่งต่าง ๆ เช่นการตรวจสอบระยะทางการเดินและอื่น ๆ จะต้องทำใน 2d แต่คุณยังต้องให้พิกัด 3 มิติสำหรับวัตถุ)
Steven Stadnicki

@LukeB แม้ว่าในกรณี OPs ฉันยอมรับว่าดูเหมือนว่าไม่มีเหตุผลที่จะสืบทอดจากVector2แต่สืบทอดมาจากฐานVector<N>? นั่นทำให้รู้สึกที่สมบูรณ์แบบ ยิ่งกว่านั้นทำไมการถ่ายทอดจึงหมายถึงพฤติกรรม polymorphic โดยอัตโนมัติ หนึ่งในสิ่งที่ดีที่สุดเกี่ยวกับ C ++ คือคุณสามารถมีการสืบทอดค่าใช้จ่ายเป็นศูนย์ ไม่จำเป็นต้องเพิ่มวิธีเสมือนใด ๆ (รวมถึงตัวทำลายเสมือน) ในVector<N>คลาสฐาน
Samaursa

5

ไม่เพราะทุกวิธีจะต้องถูกแทนที่ด้วยเช่นกันคุณจะไม่ได้รับมรดกจากมันจริง ๆ

ถ้ามีอะไรพวกเขาทั้งคู่ก็สามารถใช้ Vector interface ได้ อย่างไรก็ตามเนื่องจากคุณอาจไม่ต้องการเพิ่ม / sub / dot / dst ระหว่าง Vector2 และ Vector3 สิ่งนี้จะมีผลข้างเคียงที่ไม่พึงประสงค์ และการมีพารามิเตอร์ที่แตกต่างกันเป็นต้นจะทำให้เกิดความยุ่งยาก
ดังนั้นฉันจึงไม่เห็นข้อดีของการสืบทอด / ส่วนต่อประสานในกรณีนี้

ตัวอย่างคือกรอบ Libgdx โดยที่Vector2และVector3ไม่มีส่วนเกี่ยวข้องกันนอกจากวิธีการชนิดเดียวกัน


2

หากคุณวางแผนที่จะใช้ SIMD Arrayนั้นน่าจะดีที่สุด หากคุณยังต้องการใช้โอเปอเรเตอร์การบรรทุกเกินพิกัดคุณสามารถพิจารณาใช้อินเทอร์เฟซ / มิกซ์อินเพื่อเข้าถึงอาร์เรย์ต้นแบบ - ตัวอย่างเช่นนี่คือจุดเริ่มต้นที่มี (ไม่ได้ทดสอบ) Addเท่านั้น

สังเกตว่าฉันไม่ได้ให้X/ Y/ Zแต่ละVectorXคลาสจะสืบทอดโดยตรงจากคลาสนี้ - ด้วยเหตุผลเดียวกันกับที่คนอื่น ๆ ระบุไว้ ถึงกระนั้นฉันได้เห็นอาร์เรย์ใช้เป็นเวกเตอร์หลายครั้งในป่า

#include <xmmintrin.h>

class Vector
{
public:
    Vector(void)
    {
        Values = AllocArray();
    }

    virtual ~Vector(void) 
    { 
        _aligned_free(Values);
    }

    // Gets a pointer to the array that contains the vector.
    float* GetVector()
    {
        return Values;
    }

    // Gets the number of dimensions contained by the vector.
    virtual char GetDimensions() = 0;

    // An example of how the Vector2 Add would look.
    Vector2 operator+ (const Vector2& other)
    {
        return Vector2(Add(other.Values));
    }

protected:
    Vector(float* values)
    {
        // Assume it was created correctly.
        Values = values;
    }

    // The array of values in the vector.
    float* Values;

    // Adds another vector to this one (this + other)
    float* Add(float* other)
    {
        float* r = AllocArray();

#if SSE
        __m128 pv1 = _mm_load_ps(Values);
        __m128 pv2 = _mm_load_ps(other);
        __m128 pvr = _mm_load_ps(r);

        pvr = _mm_add_ps(pv1, pv2);
        _mm_store_ps(r, pvr);

#else
        char dims = GetDimensions();
        for(char i = 0; i < dims; i++)
            r[i] = Values[i] + other[i];
#endif

        return r;
    }

private:

    float* AllocArray()
    {
        // SSE float arrays need to be 16-byte aligned.
        return (float*) _aligned_malloc(GetDimensions() * sizeof(float), 16);
    }
};

ข้อจำกัดความรับผิดชอบ: C ++ ของฉันอาจดูดได้ไม่นานหลังจากที่ฉันใช้


เดี๋ยวก่อนการใช้งานของคุณ_aligned_mallocหมายถึงข้อผิดพลาดที่ฉันเปิดไม่ใช่ข้อผิดพลาดจริง ๆ หรือไม่?
bobobobo

คุณไม่ควรใช้พ้อยท์พอยต์เพื่อนำค่าของคุณไปสู่การ__m128ลงทะเบียนคุณควรใช้_mm_loadu_psแทน คลาสตัวอย่างที่ดีอยู่ที่นี่ภายใต้ "vectorclass.zip"
bobobobo

@bobobobo ฉันจะพยายามอย่างดีที่สุดในการแก้ไข - ระวังเป็นพิเศษกับข้อจำกัดความรับผิดชอบ;)
Jonathan Dickinson

@bobobobo _mm_loadu_psควรหาทางออกให้คุณด้วย struct _mm_load_psนั้น ฉันยังเพิ่มข้อเสนอแนะของคุณ - อย่าลังเลที่จะแก้ไขคำถามถ้าคุณรู้สึกว่าฉันเห่าต้นไม้ผิด (มันใช้เวลานานแล้วตั้งแต่ฉันใช้ C [++])
Jonathan Dickinson

1

ข้อผิดพลาดที่ร้ายแรงอีกข้อหนึ่งสำหรับการมี Vec3 สืบทอดจาก Vec2 หรือเพื่อให้มีทั้งการสืบทอดจากคลาสเวกเตอร์เดียว: รหัสของคุณจะทำมากของการดำเนินการเกี่ยวกับเวกเตอร์มักจะอยู่ในสถานการณ์ที่สำคัญและเป็นประโยชน์มากที่สุดของคุณเพื่อให้แน่ใจว่าการดำเนินการทั้งหมดนั้นเร็วเท่าที่จะเป็นไปได้ - มากกว่าสิ่งอื่น ๆ ที่ไม่ใช่ ค่อนข้างสากลหรือระดับต่ำ ในขณะที่คอมไพเลอร์ที่ดีจะพยายามอย่างดีที่สุดในการทำให้ค่าใช้จ่ายในการสืบทอดนั้นราบรื่น แต่คุณยังต้องพึ่งพาคอมไพเลอร์มากกว่าที่คุณต้องการ ฉันจะสร้างพวกเขาเป็น structs โดยให้ค่าใช้จ่ายน้อยที่สุดเท่าที่จะเป็นไปได้และอาจลองใช้ฟังก์ชั่นส่วนใหญ่ที่ใช้งานพวกเขา (ยกเว้นสิ่งต่าง ๆ เช่นโอเปอเรเตอร์ + ซึ่งไม่สามารถช่วยได้จริง ๆ ) struct โดยทั่วไปมักจะแนะนำให้ใช้การปรับให้เหมาะสมที่สุดและด้วยเหตุผลที่ยอดเยี่ยม


1
-1 เนื่องจาก: คลาสและ struct มีการให้สิทธิ์การเข้าถึงใน C ++ และ OP ไม่ได้ระบุภาษาใด ๆ ฟังก์ชันที่ไม่ใช่สมาชิกเสมือนมีผลกระทบต่อประสิทธิภาพการทำงานเช่นเดียวกับฟังก์ชันที่ไม่ใช่สมาชิกและฟังก์ชันสมาชิกเสมือน (ซึ่งอาจแสดง ปัญหาที่คุณกังวล) จะเกิดขึ้นได้ก็ต่อเมื่อคุณสร้างปัญหาไม่ใช่เพียงแค่ใช้การสืบทอด

2
@JoshPetrie คะแนนที่ถูกต้องในทุกด้าน; ฉันมักจะ 'เริ่มต้น' เป็น C / C ++ และเห็นคำถามผ่านเลนส์นั้น ผมไม่เชื่อว่ามีประสิทธิภาพการทำงานที่ถูกต้องตามกฎหมาย (เช่นเดียวกับความคิด) เหตุผลที่ไม่ไปเส้นทางมรดกใจคุณ แต่ฉันจะได้รับที่ดีมากในรายละเอียดที่เฉพาะเจาะจง ฉันจะลองและทบทวนสิ่งนี้และดูว่าฉันจะสามารถทำบัญชีให้ดีขึ้นได้ไหม
Steven Stadnicki
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.